Reputation: 401
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
Reputation: 36620
A further couple of notes on style and best practices, since wohlstad has directly answered the question.
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
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
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