MASTERSI PL
MASTERSI PL

Reputation: 91

How to free memory allocated on double pointer struct

How can I free memory allocated in this struct

struct image_t {
    char type[3];
    int **ptr;
    int width;
    int height;
};

In the first function I made these allocations:

    struct image_t *struktura = (struct image_t *)malloc(sizeof(struct image_t));
    int **ptr = (int**)malloc(struktura->height * sizeof(int*));

    for (i = 0; i < struktura->height; i++) {
        *(ptr + i) = (int *)malloc(struktura->width * sizeof(int));
        if (*(ptr + i) == NULL) {
            break;
        }
    }

In the second function I must free allocated memory so I tried to free memory something like this but it does not work

void destroy_image(struct image_t **m) {
    if (m != NULL) {
        if (*m != NULL) {
            if ((*m)->ptr != NULL) {
                for (int i = 0; i < (*m)->height; i++) {
                    free(*((*m)->ptr + i));
                }
                free((*m)->ptr);
                free(*m);
            }
        }
    }
}

I can't change declaration of the destroy function so there must be double pointer on struct.

Upvotes: 3

Views: 955

Answers (3)

4386427
4386427

Reputation: 44329

To start with...

  1. Don't cast the value returned by malloc

  2. Don't use *(ptr + i) Use the equivalent but much more readable version, i.e. ptr[i]

Doing that changes your allocation to:

struct image_t *struktura = malloc(sizeof(struct image_t));
int **ptr = malloc(struktura->height * sizeof(int*));

for (i = 0; i < struktura->height; i++) {
    ptr[i] = malloc(struktura->width * sizeof(int));
    if (ptr[i] == NULL) {
        break;
    }
}

Here is the first problem... you never assign ptr to anything. In the end of the code block you need to add:

struktura->ptr = ptr;

Another problem is that both struktura->height and struktura->width are uninitialized when used. They must be assigned a value before use.

For freeing the allocated memory: Your current code is too complex. A statement like free(*((*m)->ptr + i)); contains 3 pointer dereferences!!. That's hard to read. I'll recommend that you use a few local variables to simplify the code.

void destroy_image(struct image_t **m) {
    if (m == NULL) return;
    if (*m == NULL) return;
    struct image_t *t = *m;
    int **ptr = t->ptr;
    if (ptr != NULL)
    {
        for (int i = 0; i < t->height; i++) 
        {
            free(ptr[i]);
        }
        free(ptr);
    }
    free(t);
    *m = NULL;  // The only reason to pass a double pointer to this
                // function is to be able to change *m. I guess
                // the prototype authoe wants the function to set
                // *m to NULL
}

By using these local variables the code is much easier to read than stuff like free(*((*m)->ptr + i));

And for the break in the allocation code...

It's kind of strange (a bug?) that you you don't check for NULL after the first two malloc and then do it inside the loop. Further it's kind of strange to use break as it will leave the rest of the pointers uninitialized.

Anyway - If you really want that break in the allocation code, you also need to take that into account when freeing memory. Something like:

void destroy_image(struct image_t **m) {
    if (m == NULL) return;
    if (*m == NULL) return;
    struct image_t *t = *m;
    int **ptr = t->ptr;
    if (ptr != NULL)
    {
        for (int i = 0; i < t->height && ptr[i] != NULL; i++) 
        {
            free(ptr[i]);
        }
        free(ptr);
    }
    free(t);
    *m = NULL;
}

Upvotes: 2

chqrlie
chqrlie

Reputation: 144951

In order for your destroy function to work properly, all pointers in the pointer array must be either valid or null. Memory returned by malloc() is uninitialized so breaking out of the loop in the allocation function leaves the rest of the pointer array uninitialized, hence should not be passed to free().

Also note the you should test for allocation failure of the structure pointer and the pointer array. Furthermore the width and height members of the newly allocated structure are unintialized: you should use function arguments to initialize them.

The destroy_image function should probably set *m to NULL after deallocation and must free(*m); even if (*m)->ptr is a null pointer.

Here are solutions for this issue:

  • allocate the array with calloc() (a good idea in all cases) or
  • set height to the number of successfully allocated pointers.
  • explicitly set the remaining pointers in the array to NULL
  • free the allocated block upon allocation failure and return NULL.

Here is a modified version:

#include <stdlib.h>

struct image_t {
    char type[3];
    int **ptr;
    int width;
    int height;
};

struct image_t *allocate_image(int width, int height) {
    struct image_t *struktura = calloc(1, sizeof(*struktura));

    if (struktura == NULL)
        return NULL;

    // should initialize struktura->type too
    struktura->width = width;
    struktura->height = height
    struktura->ptr = calloc(height, sizeof(*struktura->ptr));
    if (struktura->ptr == NULL) {
        free(struktura);
        return NULL;
    }

    for (int i = 0; i < height; i++) {
        struktura->ptr[i] = calloc(sizeof(*struktura->ptr[i]), width);
        if (struktura->ptr[i] == NULL) {
            // Iterate downwards on index values of allocated rows
            // (i --> 0) is parsed as (i-- > 0)
            // this test works on signed and unsigned index types, unlike (--i >= 0)
            while (i --> 0) {
                free(struktura->ptr[i]);
            }
            free(struktura->ptr);
            free(struktura);
            return NULL;
        }
    }
    return struktura;
}

void destroy_image(struct image_t **m) {
    if (m != NULL) {
        struct image_t *p = *m;
        
        if (p != NULL) {
            if (p->ptr != NULL) {
                for (int i = 0; i < p->height; i++) {
                    free(p->ptr[i]);
                }
                free(p->ptr);
            }
            free(p);
            *m = NULL;
        }
    }
}

Upvotes: 2

Lundin
Lundin

Reputation: 214300

This is needlessly complicated and inefficient. Have a look at Correctly allocating multi-dimensional arrays.

You can change the struct like this:

struct image_t {
    char type[3];
    int* ptr; // placeholder pointer
    int width;
    int height;
};

Then malloc like this:

struct image_t* s = malloc(sizeof *s);
s->width  = width;
s->height = height;
s->ptr    = malloc( sizeof(int[width][height]) );

(Remember to check the result of each malloc and stop the program if it returns NULL.)

Then use it like this:

int (*arr)[s->height] = (void*) s->ptr; // convert to a temporary 2D array pointer
...
arr[i][j] = whatever; // this enables 2D array access syntax

And free it like this:

free(s->ptr);
free(s);

Upvotes: 2

Related Questions