user2980746
user2980746

Reputation: 401

How to free memory when casting from a void pointer in C

Question: if I have a pointer to "ints" struct as seen below and I cast it to void and back does that mean I'm not allowed to free it anymore? How does free keep track of the allocation sizes?

typedef struct ints
{
    int size;              
    int *data;       
}
ints;

static void dev_array_cleanup(void *array, array_type type)
{
    switch(type)
    {
        case INT: 
        ints *i = (ints*) array; 
        free(i->data);
        free(i); 
        i = NULL; 
        break;
    }
}

static void* dev_array_set(void *array, int size, array_type type)
{
    void *p;

    switch(type) 
    {
        case INT: 
            ints *i = (ints*) malloc(sizeof(ints));
            i->size = size; 
            i->data = (int*) malloc(size * sizeof(int)); 
            p = (void*) i;
            break;
    }

    return p;
}

Upvotes: 2

Views: 122

Answers (3)

Chris
Chris

Reputation: 36620

A further couple of notes on style and best practices, since wohlstad has directly answered the question.

  • You should check the return value from malloc and handle it if it doesn't succeed.
  • p is both unnecessary, and dangerously uninitialized if type is not INT.
static void* dev_array_set(void *array, int size, array_type type)
{
    switch(type) 
    {
        case INT: 
            ints *i = malloc(sizeof(ints));
            // If malloc failed, return a NULL pointer to indicate this.
            // A caller of dev_array_set can check this to know if the function succeeded.
            if (!i) { return NULL; }

            i->size = size; 
            i->data = malloc(size * sizeof(int));
            // Now we're at the point where the struct was allocated,
            // but the data array wasn't. We should free to struct
            // immediately and return NULL to indicate failure.
            if (!i->data) {
                free(i);
                return NULL;
            } 
            return i;
    }

    return NULL;
}

The idea of an array with a size attached is not especially idiomatic in C. Where it would make sense is implementing something like a C++ std::vector or Java ArrayList: an expandable array with constant-time access properties.

And with void pointers and judiciously applied casts, this could be made reasonably generic.

A very incomplete implementation to demonstrate:

typedef struct {
    size_t cap;
    size_t sz;
    size_t elem_sz;
    void *arr;
} vec_t;

vec_t *vec_new(size_t element_sz, size_t init_cap) {
    vec_t *v = malloc(sizeof(*v));
    if (!v) { return NULL; }

    v->cap = init_cap;
    v->sz = 0;
    v->elem_sz = element_sz;

    v->arr = malloc(v->elem_sz * v->cap);
    if (!v->arr) {
        free(v);
        return NULL;
    }

    return v;
}

vec_t *vec_grow(vec_t *v) {
    if (!v || !v->arr) return v;

    void *new_arr = realloc(v->arr, v->elem_sz * v->cap * 2);
    if (!new_arr) { return NULL; }

    v->arr = new_arr;
    v->cap *= 2;

    return v;
}

void *vec_at(vec_t *v, size_t idx) {
    if (!v || !v->arr) { return NULL; }

    return ((char *)v->arr + v->elem_sz * idx);
}

int main(void) {
    vec_t *v = vec_new(sizeof(int), 4);
    vec_grow(v);

    *(int *)((char *)v->arr + sizeof(int) * 2) = 42;

    printf("%d\n", *(int *)vec_at(v, 2));
}

A style note: your switch statement with one case is suspect. If you aren't adding more, I'd use a guard condition.

static void* dev_array_set(void *array, int size, array_type type)
{
    if (type != INT) return NULL;

    ints *i = malloc(sizeof(ints));
    // If malloc failed, return a NULL pointer to indicate this.
    // A caller of dev_array_set can check this to know if the function succeeded.
    if (!i) { return NULL; }

    i->size = size; 
    i->data = malloc(size * sizeof(int));
    // Now we're at the point where the struct was allocated,
    // but the data array wasn't. We should free to struct
    // immediately and return NULL to indicate failure.
    if (!i->data) {
        free(i);
        return NULL;
    } 
    return i;
}

Upvotes: 1

Eric Postpischil
Eric Postpischil

Reputation: 223795

How does free keep track of the allocation sizes?

Implementations of the memory management routines keep track of memory in various ways. One method is that, when memory is allocated, the routines put some data in memory before the memory that is returned to the client. For example, if a caller requests 500 bytes, the memory management software might allocate 504 bytes at address X, put the size of the allocation at address X and then return the address X+4 to the caller.

When free is called with argument Y, it calculates X = Y−4 and looks at address X to find the size.

Another method is that the memory management routines could maintain a separate database that records the sizes of all the allocations currently outstanding. When free is called, it looks up its argument in this database to see what the size is.

Another method could be that all memory in some known region is allocated in blocks of a fixed size, say 16 bytes each. (Other regions might be used for different sizes.) So when free is called with an address that is inside this known region, it knows the size is 16 bytes.

if I have a pointer to "ints" struct as seen below and I cast it to void and back does that mean I'm not allowed to free it anymore?

Converting a pointer to a different type does not change the memory it points to. When you pass a pointer to free, it is converted back to void * (because free is declared as void free(void *). As long as free receives the same address that was returned by malloc (or one of the other allocation routines), it will work.

Upvotes: 2

wohlstad
wohlstad

Reputation: 29009

I'm not allowed to free it anymore?

Yes you are allowed to free it. In fact you must (eventually) free it in order to avoid a memory leak.

free does not care about the type the pointer points to - it accepts a void*.
And any object pointer can be converted to void*.
Therefore you can simply call free on the pointer (casted or not).

How does free keep track of the allocation sizes?

This is done internally by the runtime library (based on the address of the block) and does not depend on the type.

A side note:
You should not cast the result of malloc - see here: Should I cast the result of malloc (in C)?.

Upvotes: 4

Related Questions