I have code that's got declarations like this:
typedef struct {
int a;
int b;
double c;
double d;
} Element;
typedef struct {
size_t used;
size_t capacity;
Element items[]; /* Flexible array member. */
} Container;
(The real structures are more complex.)
I want to allocate a Container
on the heap, where that container will have someNumber
of Element
slots on the end (assume, for the sake of the question, that to be an unsigned parameter passed to the function where I'm doing this). I'm currently using this:
Container *containerPtr = malloc(offsetof(Container, items) + someNumber * sizeof(Element));
This is a little long in practice (where the type names can get longer and a less trivial expression for the number of elements may be required) and has a number of places where I could make errors.
Could I validly shorten the computation of the amount to allocate to this?
Container *containerPtr = malloc(offsetof(Container, items[someNumber]));
I know this works with GCC, given how it implements offsetof
, but is it something I should expect to work in other compilers? I can't find a clear answer yes or no.
I've checked these and been left with much head-scratching:
[EDIT]: I've tried it out on godbolt and it seems very widely (but not quite universally) accepted in compilers that support flexible array members at all.
[EDIT2]: If I add this (which looks suspiciously like something in a standard header file):
#define offset_of(type, member) \
((size_t) &((type*) 0)->member)
then that works with all compilers that support FAMs, including the ones that don't work with offsetof
. For the sake of argument, I've also tried using a VLA, as then I'd be able to use sizeof
:
Container *foo(
unsigned someNumber)
{
typedef struct {
size_t used;
size_t capacity;
Element items[someNumber]; /* VLA. */
} C;
return (Container*) malloc(sizeof(C));
}
That makes GCC do something weird and unexpected with the stack before getting on with generating relevant code despite no variables with that type being declared, and many other compilers just outright refuse. (That at least is fine; they're allowed by current standards to do that.)
Could I validly shorten the computation of the amount to allocate to this?
Container *containerPtr = malloc(offsetof(Container, items[someNumber]));
Pedantically, no.
C 2024 7.21.1 says the type and member designator of offsetof(type, member-designator)
shall be such that, given static type t;
, &(t.member-designator)
is an address constant.
Given static Container t;
, &t.items[someNumber]
is not an address constant because:
someNumber
is not a constant, so the value cannot be computed during program translation (which, for the C standard, includes linking, during which expressions that are address constants can be computed).t.items[someNumber]
is not an object of static storage duration because there is no such object—the definition static Container t;
would create other members of the Container
structure, but there is no actual object t.items[someNumber]
.static Container t;
did create a populated flexible array member with someNumber
members, its last element would be t.items[someNumber-1]
. So t.items[someNumber]
would still not be an object. Unlike the specification for array arithmetic, the rule as stated in C 2024 6.6 does not contain an exception for one beyond the end of the array; it says the address constant should be the address of an object, not the address of one beyond the end of an object.Another issue is the offset of items[someNumber]
, even if it can be and is computed correctly, is not the “correct” size for the structure. Consider this structure:
struct foo
{
double d;
char c;
char array[];
};
Take double
to be eight bytes with an eight-byte alignment requirement. Then offsetof(struct foo, array[3])
is 12. However, GCC and Clang tell us that sizeof (struct foo)
is 16 bytes, because they include additional space for padding. With an ordinary structure (no flexible array member), that padding is needed to form an array of the structure with properly aligned elements. That is not the case with structures with flexible array members, since they cannot be formed into arrays ordinarily. However, the compiler is entitled to use its knowledge of the size of the structure. For example, in some code working with members of the structure, the compiler might use instructions that load and store multiple bytes outside the explicit members because it expects the structure to contain at least 16 bytes. If not enough memory has been allocated for the structure, those instructions could improperly access memory outside that allocated for the structure.
I do not expect this to be a problem in practice, particularly if the offsetof
compiles without complaint from the compiler, but the code pedantically does not conform to the rules of the standard.