1

We got a 2D allocation as follows:

char** arr;
short i = 0;

arr = malloc(sizeof(char*)*10);
for( ; i<10; i++)
{
    arr[i] = malloc(sizeof(char)*100);
}

which allocates arr[10][100]; Then we need to free it like so:

for(i=0; i<sizeof(arr); i++) free(arr[i]);
free(arr);

So i was thinking to make a function that saves time, space and confusion and i have this:

void pfree(void** data)
{
    int i;

    for(i=0; i<sizeof(data); i++)
    {
        free(data[i]);
    }
}

Is the function relevant, if not is there another way?

Edenia
  • 2,312
  • 1
  • 16
  • 33

4 Answers4

6

It's completely reasonable to create functions like this. Especially if it:

  1. Increases the clarity of your code
  2. Decreases the likelihood of bugs

I would probably write a malloc_2d() and free_2d():

void free_2d(void **arr, size_t count) {
    int i;

    if (arr) {
        for (i=0; i<count; ++i)
            free(arr[i]);
        free(arr);
    }
}

void** malloc_2d(size_t count, size_t elem_count, size_t elem_size) {
    int i;        
    void **arr = calloc(count, sizeof(void *));

    if (arr == NULL)
        return NULL;

    for (i=0; i<elem_count; ++i) {
        arr[i] = calloc(elem_count, elem_size);
        if (arr[i] == NULL) {
            free_2d(arr, i);
            return NULL;
        }
    }

    return arr;
}

Alternatively, a common mechanism is to encode a 2-dimensional array into a 1 dimensional array. So, let's say we want a 16x4 array.

int *arr = malloc(sizeof(int) * 6 * 4);
for (int i=0; i<16; ++i)
    for (int j=0; j<4; ++j)
        arr[i*4 + j] = compute(i, j);
The Mask
  • 17,007
  • 37
  • 111
  • 185
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • Thank you for your time spent on this. What you have wrote is perfect. :) – Edenia Jun 01 '14 at 14:41
  • Since this is about C, you can use real 2D arrays instead of computing an index into a 1D array. Just replace the allocation in your last example with `int (*arr)[4] = malloc(6*sizeof(*arr));` and the array access `arr[i*4 + j]` with `arr[i][j]`. The memory layout is the same, but the compiler will do the index computation for you. – cmaster - reinstate monica Jun 01 '14 at 14:44
  • @cmaster: I'd argue that the memory layout in your code is better. (And there's changes that could be made in my code to reach that goal as well). That being said, it complicates things, and that's probably not helpful to Edenia. Additionally, your suggestion requires knowledge of the array width at compile time (which I usually don't know until runtime). – Bill Lynch Jun 01 '14 at 14:53
  • 1
    It seems, I have good news for you: in C array sizes **don't** have to be compile time constants anymore (I believe since C99). That is one big place where C is much more flexible than C++ which retains the compile time constant rule. – cmaster - reinstate monica Jun 01 '14 at 14:59
  • Isn't `free_2d(arr, i);` instead of `free_2d(arr, count);`? because allocation might fail in loop before `count` is reached. – The Mask Jun 01 '14 at 16:31
  • @TheMask: Whoops. I meant to have a fix in there for that. Should be good now. – Bill Lynch Jun 01 '14 at 16:39
  • 1
    Are void** and int**( or any other type ) compatible? I don't think so. If so, this violates aliasing rules. +edit: You can use void** type outside but then you have to cast everytime which is error prone and makes the code unclear. – this Jun 01 '14 at 19:50
  • @self.: I believe you are correct. Do you know of a reasonable solution? (nothing comes to my mind...) – Bill Lynch Jun 01 '14 at 20:22
  • @sharth You can't really do it with an array of pointers( unless you use void* and get/set functions ), but since OP doesn't need a jagged array, just allocate a single block of memory and return to a pointer to an 2d array. int (*arr)[inner_size] = malloc( sizeof( int ) * size * inner_size ) ; – this Jun 01 '14 at 20:29
3

First you need to understand that pointers are not arrays. sizeof operates differently on both types.
When the parameter of sizeof is a pointer then it simply returns the size of pointer, which is either 4 or 8 bytes.
When the parameter of sizeof is an array name then it returns the size of the entire array. For example; for int a[10], sizeof(a) will return 10 * sizeof(int) = 40 bytes (taking 4 bytes for an int).
So, the loop

for(i=0; i<sizeof(arr); i++) free(arr[i]);  

this doesn't work as you expect. You need to change it to

for(i=0; i<10; i++) free(arr[i]); 

And change your function to

void pfree(void** data, int size)
{
    int i;

    for(i=0; i<size; i++)
    {
        free(data[i]);
    }
    free(data);
}
Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
  • Yes. I was confused because the debug aways says that arrays are actually pointers. And it makes sense. I understand the difference like.. arrays don't point to another adress but their next one. – Edenia Jun 01 '14 at 14:47
  • No. Arrays are not pointers which does make sense! [**pointer arithmetic and array indexing (that) are equivalent in C, pointers and arrays are different**](http://c-faq.com/aryptr/aryptrequiv.html). – haccks Jun 01 '14 at 14:51
  • Ahh i see. Arrays point to an adress though, thats why i assume them pointers but they are not like the real ones, marked with the astersk. – Edenia Jun 01 '14 at 14:55
  • YES. I included links in my answer and comment. That would be helpful. – haccks Jun 01 '14 at 14:56
  • That is helpful. It is aways nice to refresh your knowledge about the pointers. – Edenia Jun 01 '14 at 14:57
  • I think is valid to mention C99's sizeof and VLAs either. OP might find a working program where size of array is take at runtime using `sizeof` operator and turns confusing. – The Mask Jun 01 '14 at 16:36
  • @TheMask; I think that's a different issue. – haccks Jun 01 '14 at 16:46
1

The size of an array is encoded in its type, so whenever you pass only a pointer to its first element, that information is lost, sizeof will only yield the size of the pointer. Thus you cannot use the signature the way you wrote it, you must supply the size of the array:

void pfree(void** data, size_t count) {
    for(size_t i = count; i--;) free(data[i]);
    free(data);
}

Note, that the lines

for(i=0; i<sizeof(arr); i++) free(arr[i]);
free(arr);

is wrong for memory allocated with

char** arr;
short i = 0;

arr = malloc(sizeof(char*)*10);
for( ; i<10; i++) {
    ...

as well, because sizeof(arr) only gives the size of a pointer again!

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
0

You could simplify the freeing by allocating enough space for the whole 2D array. This works quite well if elem_count * elem_size is a multiple of 4 on a 32 bit system or a multiple of 8 on a 64 bit system.

void pfree(void **arr, size_t count) 
{
    if (arr)
    {
        /* arr[0] points to a contiguous array so no need to go through
           the whole array */
        free(arr[0]);
        free(arr);
    }
}

void** pmalloc(size_t count, size_t elem_count, size_t elem_size)
{
    int i;        
    void **result = calloc(count, sizeof(void *));
    /* Allocate enough space for the entire array */
    int arr_size = elem_count * elem_size;
    char *arr = calloc(count, arr_size);

    if (arr == NULL || result == NULL)
    {
        /* may get a memory leak */
        return NULL;
    }

    for (i=0; i<count; ++i)
        result[i] = &arr[i * arr_size];

    return result;
}
cup
  • 7,589
  • 4
  • 19
  • 42