Memory Ownership in C Explained

The memory ownership problem or pointer ownership problem is a problem generally seen in C and C++ programs. The problem is generally violating a simple assumption that there should be only one pointer to a given piece of memory, so that when the memory is freed, no other pointers will point to an address that has already been freed.

For example, you have a main function, in which a pointer gfcontext_t *ctx to a struct gfcontext_t is created. Let’s say the ctx is located at address y, and its value is x, which is the address of the actual created data structure. If the address now need to be passed to a second function for additional process, if we directly pass ctx to the function, in the scope of this function, a local pointer variable at address z will be created, and it holds exactly same value as the passed-in ctx, i.e y, because function arguments are passed-by-value in C. What this implies is that there will be two pointers to the y, one in the main function, one in the second function. If the second function decide to free the memory of the data because it finished processing, the other pointer in the main function will not be affected, and it still holds the address to the data, which is just freed by the second function. The second function have no way to modify the ctx pointer in the main function because the function does not have the address of ctx.

A simple way to avoid such problem is to use a pointer-to-pointer as the function argument. The figure below illustrate the pointer relationship.

memory ownership

Specifically, by passing &ctx to the second function, the function will create a local variable at address z which holds the address of the pointer *ctx, i.e y in the main function. Now, if the second function need to free the data, all it needs to do is dereferencing the passed-in pointer-to-pointer, which gives us the value in the address of ctx, i.e x. Then we just call free() on the dereferenced pointer-to-pointer because is is the same as free(x). After the data is properly freed, the pointer ctx in the main function is still lingering there, but now we have a ‘handle’ of ctx in the scope of the second function, i.e the passed-in address of that variable. Therefore, we can set the value in that address to NULL and everything is properly cleaned.

The high-level code of the above process is as below:

void main() {
    gfcontext_t *ctx = malloc(sizeof(gfcontext_t));
    func(&ctx);
}

void func(gfcontext **c) {
    // do something...
    free((*c));
    (*c) = NULL;
}