2

In the following code, will freeing Dairy free Yogurt as well?
Both point to the same address as far as I know.

Also, is this style of coding a bad practice? Say if I only kept pointers to the Dairy and indirectly freed the Yogurt and Cheese as well?

#include <stdlib.h>

typedef struct {
    int calcium;
    int protein;
} Dairy;

typedef struct {
    Dairy dairy;
    int sugar;
    int color;
} Yogurt;

int main () {
    Yogurt* yogurt = malloc(sizeof(Yogurt));

    Dairy* dairy = &yogurt->dairy;

    free(dairy); // Will this free yogurt?
}
Alok Save
  • 202,538
  • 53
  • 430
  • 533
G M
  • 67
  • 3

5 Answers5

4

Yes the behavior is well-defined.

As per the C Standard You need to pass the exact same address to deallocation function free as was returned by an malloc or other allocation functions.

Reference:

7.22.3.3 The free function

Synopsis:

§1 #include <stdlib.h> void free(void *ptr);

Description:

§2 The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

So, In this case the behavior is guaranteed to be well defined if and only if pointer to the structure is same as pointer to it's first member.
And that is guaranteed by the standard in:

6.7.2.1 Structure and union specifiers
§15

Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • The pointers to dairy and yogurt matched exactly when I printed them. Would I be able to rely on this or is it compiler specific? – G M Feb 08 '12 at 07:45
  • @GM: Its well defined, Updated the reference from the Standard. – Alok Save Feb 08 '12 at 07:55
2

There is a related discussion here: Is this code guaranteed by the C standard?

It's referring to the C standard and what is defined with regards to padding. Padding is never done at the beginning of a struct, thus the code will work. I would even go as far as to say that this is a quite common pattern found in a lot of C-code.

Community
  • 1
  • 1
  • Thank you, I didn't notice this post. It's good to hear that this pattern is used but I hesitate to use it because it seems fragile like Michael Burr says. – G M Feb 08 '12 at 08:14
1

A pointer to a structy and a pointer to the first member of a struct must point to the same address:

C99 6.7.2.1/13 Structure and union specifiers

A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

So, you can free() either expression, though I wouldn't recommend it in the style shown since it seems rather fragile and confusing to readers, I think. But I could see it making sense (and being useful) if you're implementing a kind of C object-based APIs that expect a 'base class' pointer and expect to be able to free it.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
0

It will. free takes a void pointer, and will free whatever it is that you tell it to free. Since this is C there are no destructors to worry about, in contrast to C++

Note that this only works because Dairy is declared first in your "inherited" struct. If you change the declaration order, this will no longer be functional.

Edit: On second thought, you cannot rely on this. You have no idea what compiler magic might happen and what packing might happen that might offset the Dairy member in your struct. It will probably work, but I'm not sure you're guaranteed to have it working always.

It's pretty safe to say that this is bad practice and might result in portability problems later on. Especially for platforms that pack structs differently and use different native word alignment. It might also be messed up with #pragma pack and __declspec(align())

Dervall
  • 5,736
  • 3
  • 25
  • 48
0

If you wanted to be able to free the Dairy separately you could replace the Dairy in Yogurt with a Dairy*. You'd also have to allocate it separately, though.

James Gilles
  • 674
  • 6
  • 12