Saturday 11 October 2014

Dynamic Memory in C (malloc, calloc, realloc, free)

Let’s take a look at the four methods that allow us to utilize dynamic memory in C. Dynamic memory just means we are using memory on the heap, instead of on the stack.

Why would you want to use dynamic memory?

You might want to create a variable or object that persists beyond the scope of the function it is created in (i.e. to share it between functions). The only way to do this is to create it dynamically, and then remember to deallocate it at a later point. Alternatively, you might not know the size of something you are using until runtime (e.g. reading in a file), in which case the heap is a better place to store your data. Why? Because the size of the stack is limited and it may be too small for unknown data processed at runtime.

How do you dynamically allocate in C?

There are three functions for memory allocation (plus one for deallocation). Each allocator returns a void* to the memory that has been allocated, which can be cast to the appropriate type.
Let’s look at each in turn:
1. malloc
This is the most commonly used method. Simply pass in how big you want your memory to be (in bytes), and you get a pointer to that memory back. The memory is uninitialized. If it fails it returns NULL.
2. calloc
Instead of passing in a size, you tell calloc how many of a certain type of variable you are going to use. E.g. 10 ints, or 16 structs. The memory is initialized to zeros. If it fails it returns NULL.
3. realloc
This method resizes an existing block of memory and you can make your existing memory allocation bigger or smaller. It frees the existing block and returns a void* to the new block. If you pass in zero, it effectively frees the memory in question. If it fails it returns NULL (see the comments in the code below for why you should pay careful attention to how you use realloc).

And what do you do when you’re finished?

You call free, nice and easy, and your memory is released (although it is not “deleted” as such – the data may exist until something else overwrites it, or part of it, or until the program ends).
Note that calls to malloccalloc and realloc set errno if they fail.

Can I see an example?

Sure – here’s a little bit of code that uses all four methods so you can see them at work:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdlib.h>
#define BIG_NUMBER 1024
#define SMALL_NUMBER 16
struct msg
{
    int code;
    char message[BIG_NUMBER];
};
int main(void)
{
    char* buffer;
    struct msg* messagelist;
    /* Allocate some memory from the heap */
    buffer = (char*)malloc(BIG_NUMBER);
    if (buffer != NULL)
    {
        /* I can use the memory safely */
    }
    /* Reduce the size of the memory */
    char* smallbuffer = (char*)realloc(buffer, SMALL_NUMBER);
    if (smallbuffer != NULL)
    {
        /* I can use the memory safely */
    }
    /*******************************************
     * NOTE: Look carefully at the realloc call above.
     * If the call to realloc had failed and I had assigned
     * it to the original buffer like so:
     *     buffer = (char*)realloc(buffer, SMALL_NUMBER);
     * then my buffer would have been set to NULL and I would
     * not only lose access to the data that was stored
     * there, but I'd create a memory leak too!
     *******************************************/
    /* Allocate some memory from the heap */
    messagelist = (struct msg*)calloc(SMALL_NUMBER, sizeof(struct msg));
    if (messagelist != NULL)
    {
        /* I can use the memory safely */
    }
    /* Remember to clear up after myself */
    free(smallbuffer);
    free(messagelist);
   
    return EXIT_SUCCESS;

No comments:

Post a Comment