cflexible-array-memberoffsetof

Using offsetof to get allocation size


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.)


Solution

  • 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:

    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.