Skip to content

Edge case with offsetof #549

Open
Open

Description

@dkfellows

I've been trying out using offsetof to calculate the size of structures with flexible array members, and CompCert appears to be the only compiler that fails this test case (where flexible array members are supported at all). I'm not sure if it should fail, but you might want to know.

Note that I'm not 100% whether this code is supposed to work according to the standard.

Test case code

#include <stddef.h>
#include <stdlib.h>

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;

Container* bar(
    unsigned someNumber)
{
    return malloc(offsetof(Container, items[someNumber]));
    /* Other initialisation omitted. */
}

Expected outcome

I'd expect to get a function that allocates memory of the stack where the size of the FAM is large enough to take someNumber elements.

What I got

(Running on godbolt with version 3.12)

<source>:20: error: array element designator is not an integer constant expression
1 error detected.

Activity

  1. xavierleroy commented on May 6, 2025

    @xavierleroy
    Contributor

    That's an interesting edge case, indeed !

    The C11 standard says:

    offsetof(type, member-designator)
    which expands to an integer constant expression that has type size_t, the value of
    which is the offset in bytes, to the structure member (designated by member-designator),
    from the beginning of its structure (designated by type). The type and member designator
    shall be such that given
    static type t;
    then the expression &(t.member-designator) evaluates to an address constant.

    Note the "integer constant" and "address constant" clauses. I don't think this condition is met in your example. So, I think CompCert is not wrong to reject it. But it is surprising indeed, since the naive implementation of offsetof(ty, memb) as

        (size_t) (char *) (((ty *) NULL)->memb)
    

    seems to work even if memb contains a non-constant array reference.

  2. SamB commented on Mar 9, 2026

    @SamB

    ... yeah, arguably the standard should only require

    the expression &(t.member-designator) evaluates to an address constant.

    for the expansion to be an integer constant expression.

    But I'm pretty sure that the implementation

    #define offsetof(ty,  memb) (size_t) (char *) (((ty *) NULL)->memb)

    is undefined behavior, though I guess if a compiler knows it won't exploit that UB it is free to define offsetof that way, and that the approach hinted at by the standard, where we consider the address of the designated member of a statically allocated object, would also involve undefined behavior if someNumber * sizeof(Element) is greater than sizeof(Container) - offsetof(Container, items), which I'm fairly certain means it is defined only for someNumber == 0 (given the specific structs above).

    Now it would be really nice to change this, but I think that either requires the spec to hypothesize a magic builtin just for this, or to hand-wave away the array-indexing rules somehow, because there is no way to get a suitable object to use to form a pointer to arbitrary flexible-array items: to even know how big the object would be, you would need offsetof ...

    Now GCC has a builtin for offsetof, but it's not clear that it allows arbitrary flexible array indexing.

    So it's probably tricky to actually change the standard to allow this :-(.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions