cstructtypes

Clarification regarding flexible array member access


From C23 §6.7.2.1(20):

[...] when a . (or ->) operator has a left operand that is a (pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array.

I am not sure I understand the last sentence. Why would the offset of the replacement array differ from that of the flexible array member (FAM)? If the replacement array has the same element type as that of the FAM, then wouldn't it have the same alignment as the FAM and, therefore, the same offset as the FAM?


Solution

  • The C standard doesn't describe much detail how padding is laid out in a structure. The only requirement is that there is no padding at the beginning.

    Specifically, section 6.7.2.1p17 of the C23 standard states:

    ... There may be unnamed padding within a structure object, but not at its beginning

    And 6.7.2.1p19 states:

    There may be unnamed padding at the end of a structure or union

    So for example, given the following structs:

    struct foo1 {
        int i;
        double d;
        int f[3];
    };
    
    struct foo2 {
        int i;
        double d;
        int f[];
    };
    

    The compiler would be free to lay them out as follows:

    // total size: 32 bytes
    struct foo1 {
        int i;                // offset 0
        // 4 bytes padding
        double d;             // offset 8
        // 4 bytes padding
        int f[3];             // offset 20
    };
    
    // total size: 16 bytes + flexible array member
    struct foo2 {
        int i;                // offset 0
        // 4 bytes padding
        double d;             // offset 8
        int f[];              // offset 16
    };
    

    Then if you created an instance of struct foo2 with 3 flexible array members as follows:

    struct foo2 *x = malloc(sizeof(struct foo2) + 3 * sizeof(int));
    

    The "replacement array" in this case would have type int[3] as that is the maximum allowed given the actual object allocated.

    The offset of x->f would be required to be 16 as per the passage you marked in bold, even though an instance of struct foo1, whose f member is the same size as the replacement array above, would have that member at offset 20.