When do nondynamic arrays get free'd in a struct

Viewed 179

I have the following struct

typedef struct  {
    int id;
    double c[2];
    double p[4][2];
} Detection;

This is the constructor for it

Detection* create_detection(int i) {
    Detection* detection = (Detection*) malloc(sizeof(Detection));
    detection->id = i;
    return detection;
}

As you can see, the struct itself is dynamically allocated. I'm trying to write a destructor for it and this is what I have so far.

void destroy_detection(Detection* detection) {
    free(detection);
}

Will this free c and p as well?

4 Answers

There is only one allocation here. The c and p fields do not have separate allocations. The general rule is each call to malloc must be balanced, one to one, by a call to free. The destructor as written is all that is needed. If there were other dynamically allocated pointers inside the struct, additional free calls might be required.

Note that the c and p fields have fixed size, which is included in sizeof(Detection). In C++ fields can have their own constructors which can do dynamic allocation, but they will also typically be automatically destructed from code the compiler generates in the parent destructor.

C has variable length arrays (VLAs) but they cannot be declared in a struct, only in a function argument list or in a block inside a function.

I want to explicitly state the important part that is implied in other answers:

The malloc does not know what it allocates memory for.

It receives the number of bytes as an input and returns a pointer, that has at least that many bytes addressable after it.

On my compiler/machine combination sizeof(Detection) is 88. So malloc just allocates 88-ish bytes in heap and returns a pointer to it. It is the programmer's responsibility to interpret that memory in certain way (and compiler's responsibility to help with the layout the struct members in memory in a correct fashion and generate proper code that given the pointer to memory and a member name it would calculate proper offset inside that memory to access that member data) - in your code you assign that address to a pointer to the Detection struct. You could also try assigning it to a pointer to another struct and it might or might not work (as in program crashes and/or wipes all the data on your computer) depending on the struct size and the amount of memory allocated.

So, malloc, does not know that it allocates memory for a struct containing arrays. It just allocates continuous region of memory, that if coded properly (i.e. proper size passed to malloc) would be enough to contain a struct of type Detection.

We can modify your code a bit (I cheated a bit, since on my machines sizes of long long and double happen to be the same, don't do that):

#include <stdlib.h>
#include <stdio.h>

typedef struct {
    int id;
    double c[2];
    double p[4][2];
} Detection;

int main()
{
    size_t size = sizeof(Detection);
    printf("sizeof(Detection) = %u\n", size);
    Detection *detection = malloc(size);
    detection->id = 0x01020304;
    *(long long *)(&detection->c[0]) = 0x1011121314151617;
    *(long long *)(&detection->c[1]) = 0x18191A1B1C1D1E1F;
    *(long long *)(&detection->p[0][0]) = 0x2021222324252627;
    *(long long *)(&detection->p[0][1]) = 0x28292A2B2C2D2E2F;
    *(long long *)(&detection->p[1][0]) = 0x3031323334353637;
    *(long long *)(&detection->p[1][1]) = 0x38393A3B3C3D3E3F;

    *(long long *)(&detection->p[2][0]) = 0x4041424344454647;
    *(long long *)(&detection->p[2][1]) = 0x48494A4B4C4D4E4F;
    *(long long *)(&detection->p[3][0]) = 0x5051525354555657;
    *(long long *)(&detection->p[3][1]) = 0x58595A5B5C5D5E5F;
}

Then run it in the debugger to see the memory layout, which in my case is (starting from the third line the 0x01097018 address):

0x01097008  01 00 00 00 58 00 00 00 4e 00 00 00 fd fd fd fd  ....X...N...ýýýý
======== detection starts here =========
0x01097018  04 03 02 01 cd cd cd cd 17 16 15 14 13 12 11 10  ....ÍÍÍÍ........
0x01097028  1f 1e 1d 1c 1b 1a 19 18 27 26 25 24 23 22 21 20  ........'&%$#"! 
0x01097038  2f 2e 2d 2c 2b 2a 29 28 37 36 35 34 33 32 31 30  /.-,+*)(76543210
0x01097048  3f 3e 3d 3c 3b 3a 39 38 47 46 45 44 43 42 41 40  ?>=<;:98GFEDCBA@
0x01097058  4f 4e 4d 4c 4b 4a 49 48 57 56 55 54 53 52 51 50  ONMLKJIHWVUTSRQP
0x01097068  5f 5e 5d 5c 5b 5a 59 58

From the values in memory you can easily see how the struct is laid out in memory, at can notice that arrays are embedded into that memory layout.

As a consequence, free also has absolutely no idea what type of struct sits on the other end of the pointer passed to it. It simply receives an address that was [allegedly] previously returned by malloc (a void *) and knows how to deallocate it as a chunk of memory (not as a struct of certain type). There is no kind of "destruction" happening other than marking the memory as "not used".

I added an additional 16 bytes of memory before the address returned by the malloc to demonstrate how free knows about how many memory to deallocate (this is totally implementation dependent, by the way, and you should not rely on it in any way). You may notice that 58 in the first line which translates to decimal 88 - it is the size of the memory allocated

One can easily pass size + 42 to a malloc, it would still be correct program, it just would waste the memory.

Will this free c and p as well?

Yes.

Arrays c and p are statically allocated, meaning that will take care the memory management for you; you don't have to worry about it.


Rule of thumb: Call free() as many times as you called malloc().

Here you dynamically allocated memory once, thus you must de-allocate that dynamic memory once, only.

The memory for c and p is allocated inline in the struct. This is because c and p as arrays arrays and not pointers. If In C pointers and arrays are not the same thing. Were c and p defined as pointers you would then have to both allocate and free memory for them before using them.

The reason for the confusion is that arrays can sometimes decay into pointers and vice versa. an example of this is the equivalence of double* ptr = c and double* ptr = &c[0] (c is an array). Pointer decay happens when accessing the array and does not happen when we define the array.

Related