Search code examples
cmallocansi-c

Why does free work like this?


Given the following code:

typedef struct Tokens {
    char **data;
    size_t count;
} Tokens;

void freeTokens(Tokens *tokens) {
    int d;
    for(d = 0;d < tokens->count;d++)
        free(tokens->data[d]);
    free(tokens->data);
    free(tokens);
    tokens = NULL;
}

Why do I need that extra:

free(tokens->data);

Shouldn't that be handled in the for loop?

I've tested both against valgrind/drmemory and indeed the top loop correctly deallocates all dynamic memory, however if I remove the identified line I leak memory.

Howcome?


Solution

  • Let's look at a diagram of the memory you're using in the program:

    +---------+       +---------+---------+---------+-----+
    | data    |  -->  | char *  | char *  | char *  | ... |
    +---------+       +---------+---------+---------+-----+
    | count   |            |         |         |
    +---------+            v         v         v
                         +---+     +---+     +---+
                         | a |     | b |     | c |
                         +---+     +---+     +---+
                         |...|     |...|     |...|
                         +---+     +---+     +---+
    

    In C, we can dynamically allocate space for a group (more simply, an array) of elements. However, we can't use an array type to reference that dynamic allocation, and instead use a pointer type. In this case, the pointer just points to the first element of the dynamically allocated array. If you add 1 to the pointer, you'll get a pointer to the second element of the dynamically allocated array, add two to get a pointer to the second element, and so on.

    In C, the bracket syntax (data[1]) is shorthand for addition and dereferencing to a pointer. So pointers in C can be used like arrays in this way.

    In the diagram, data pointing to the first char * in the dynamically allocated array, which is elsewhere in memory.

    Each member of the array pointed to by data is a string, itself dynamically allocated (since the elements are char *s).

    So, the loop deallocates the strings ('a...', 'b...', 'c...', etc), free(tokens->data) deallocates the array data points to, and finally, free(tokens) frees the entire struct.