July 11, 2017·4 min read·engineeringcstandardsmemory

Flexible arrays expose an ambiguity in C structure padding

N2159 asks whether writing a structure member can disturb flexible-array elements that overlap trailing padding. Its proposed correction separates member updates from whole-structure assignment.

Writing a structure's length field should leave its payload alone. Jens Gustedt's N2159 defect report explains why C11's wording makes that expectation uncertain for some flexible-array layouts. The first elements of a flexible array can occupy bytes within the size of the structure itself. The standard also describes trailing bytes as padding, whose values may become unspecified when another member receives a value.

The WG14 document log lists N2159 on July 11. The paper is dated July 6 and presents questions and a suggested technical corrigendum, not an adopted change. Its importance lies in the interaction between two existing rules, rather than a request for a new kind of allocation.

I find this kind of defect work essential to C's usefulness as an interface language. A structure layout is only part of the agreement. Programs also need to know which stored values a member assignment may affect, especially when a compact representation places payload bytes within the structure's nominal extent.

Two descriptions apply to the same bytes

The paper begins with a structure containing a size_t, a single unsigned character and a flexible array of unsigned characters. Its example assumes a layout where three array elements fit before the end identified by sizeof for the structure. An additional allocated byte makes room for a fourth element beyond that boundary.

This declaration shows the relevant shape without assuming a particular target's padding count. The offset and size expressions are separate quantities whose relationship depends on the implementation.

#include <stddef.h>

struct record {
    size_t length;
    unsigned char status;
    unsigned char payload[];
};

size_t payload_offset(void)
{
    return offsetof(struct record, payload);
}

N2159 cites C11's rule that the structure's size treats the flexible member as omitted, with a qualification allowing additional trailing padding. It then cites the rule under which padding bytes take unspecified values when a value is stored in a structure or one of its members. Applying both descriptions raises a problem for bytes that an application accesses as initial payload elements.

In the paper's assumed layout, the program initializes four array elements and then assigns the length member. Gustedt's reading leaves the first three elements with unspecified values because they overlap the nominal trailing padding. The fourth element, beyond that part of the structure, retains its initialized value. The example isolates the issue with a layout assertion, so its numerical padding assumption is not a universal claim about C implementations.

Member assignment needs a narrower effect

Gustedt proposes that assigning a non-array member preserve the flexible array's bytes. The suggested wording prohibits modifying bytes beyond the flexible member's offset during such an assignment. This directly addresses the problematic length update, since the payload can begin before the end of the structure's sizeof extent.

The proposed treatment also reaches structures contained within unions. N2159 asks that flexible-array bytes within the relevant enclosing storage count as array elements instead of padding. That matters to the type and value of those elements, not merely to whether sufficient memory exists at their addresses.

Effective type is another explicit part of the report. Its longer example compares a declared structure, storage that receives a structure assignment and storage returned by calloc. The questions distinguish what type an overlaid element has from what value it contains. Available storage, a usable element type and an initialized value are separate requirements, so answering only the size question leaves the report's central concerns unresolved.

Whole-structure assignment remains different

The paper does not propose that structure assignment become a payload-copy operation. Its suggested model permits overlaid flexible-array elements to take unspecified values after assigning the whole structure. An implementation may copy bytes that happen to include those elements, but the operation does not promise to copy the flexible payload as a logical sequence.

That distinction supports a coherent interface rule. Updating the length member should preserve separately stored elements, while copying the fixed structure object need not copy an arbitrarily extended array. Treating those operations separately also avoids making payload length an implicit property of the assignment operator, whose operands still have the structure type.

N2159 separately asks about initialization syntax for flexible members. It notes implementation extensions and proposes clarification that the incomplete member type is not completed by an initializer. Acceptance by a compiler therefore cannot alone establish the portable meaning of such initialization. Gustedt reports experiments with GCC and Clang, but presents those observations separately from the normative text he wants changed.

I favor the proposed preservation rule for member updates because it matches the separation that the declaration expresses. The length field and payload have different roles, even when layout places early payload bytes within the fixed structure extent. The report's assignment distinction is equally useful. A fixed-structure assignment still needs a separate account of payload copying, while a member update should preserve the array elements that already have stored values.