• Re: Storage needed when there are bit-field members

    From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Fri Aug 14 14:19:32 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    But
    uint32_t bf : 1;
    is meaningfully different from
    unsigned bf : 1;

    only because in most implementations (and ABIs), the underlying type
    of a bit field affects the layout of the entire structure.

    [...]

    I accept that this is the case, but it's never made any sense to me,
    and there's no hint of it in the C standard.

    I think saying there is not even a hint is an overstatement. The C
    standard says that an implementation "may allocate any addressable
    storage unit large enough to hold a bit-field." It shouldn't be a
    surprise that how much storage is allocated depends on the type of
    the bit-field member. For example, a bit-field of type 'unsigned'
    might very well choose a larger storage unit than what is chosen
    for a bit-field of type '_Bool'. It seems obvious that the type of
    a bit-field might affect what size and layout is chosen.

    I'm sure it seems obvious to you. As I said, it's not at all
    obvious to me.

    Prior to C99, C didn't even require compilers to support bit-field
    types other than int, unsigned int, and signed int.

    True, but allowing other types was listed as a common extension.

    The declared
    type might typically be used only to determine the signedness of the bit-field (though I *think* most compilers permitted other types).

    Implementations are certainly not *required* to use the declared
    type of a bit-field as a factor in deciding how to allocate it,
    or how to allocate the rest of the structure. Allocating just one
    byte for an isolated 1-bit bit-field of any declared type would
    be conforming. A conforming compiler could use the declared type
    only to determine the signedness and the maximum allowed width of
    a bit-field (and its conversion behavior in the case of bool)

    Yes, it could.

    For example, if I write:
    uint64_t bf : 1;

    then the containing struct is typically at least 64 bits, even
    though those other 63 bits aren't part of the bit field and other
    members can be allocated within them.

    It would make a lot more sense *to me* if an N-bit bit field were
    simply N bits.

    Two problems with that. One, it seems to be in conflict with what
    the C standard says about 0-width bit-fields.

    0-width bit-fields are obviously a special case.

    Sorry for not making my point more clear. My comment is meant to
    to raise the question of whether

    struct x {
    _Bool foo:1;
    _Bool :0;
    char c;
    };

    and

    struct y {
    unsigned foo:1;
    _Bool :0;
    char c;
    };

    should be different. I'm inclined to think they should be, by
    which I mean my preference is for compilers where they would be.

    Two, the C standard
    explicitly allows allocating bit-fields using a high-to-low order
    or a low-to-high order (implementation-defined choice). Presumably
    this freedom is given to accommodate both big- and little-endian
    platforms. The idea that an N-bit bit-field should simply be N
    bits doesn't work in big-endian environments. It seems better to
    allow little-endian implementations to choose a size that matches
    what a big-endian implementation would use, rather than insisting
    that they be different.

    I honestly don't understand your point here. How does making
    N-bit bit-fields N bits not work in a big-endian environment?
    Can you elaborate? Of course endianness can affect how bit-fields
    are allocated within a "storage unit".

    Suppose we have a little endian machine where bit-fields are
    allocated in a high-to-low order. Further suppose that unsigned
    ints are 32 bits. In such an environment, I would expect (or
    prefer) a definition like this

    struct foo {
    unsigned x:15;
    };

    to be represented like so

    -------- -------- -XXXXXXX XXXXXXXX

    where the X's indicate where the bit-field goes, and the -'s
    indicate where there are padding bits. In such an environment,
    I would find it counterintuitive if this type were represented
    thus

    -XXXXXXX XXXXXXXX

    rather than as shown in the previous layout.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Fri Aug 14 14:56:00 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    It would make a lot more sense *to me* if an N-bit bit field were
    simply N bits.

    [...]

    Two, the C standard
    explicitly allows allocating bit-fields using a high-to-low order or
    a low-to-high order (implementation-defined choice). Presumably
    this freedom is given to accommodate both big- and little-endian
    platforms. The idea that an N-bit bit-field should simply be N bits
    doesn't work in big-endian environments. It seems better to allow
    little-endian implementations to choose a size that matches what a
    big-endian implementation would use, rather than insisting that they
    be different.

    I honestly don't understand your point here. How does making
    N-bit bit-fields N bits not work in a big-endian environment?
    Can you elaborate? Of course endianness can affect how bit-fields
    are allocated within a "storage unit".

    Perhaps you read more than I intended into my statement about N-bit bit-fields being "simply N bits".

    It's possible, but I don't think I did.

    Thinking about this a bit more.

    As of C90, "A bit-field shall have a type that is a qualified or
    unqualified version of one of int, unsigned int, or signed int."
    The "shall" is outside a constraint, so an implementation could allow bit-fields of other types without triggering a required diagnostic,
    and many implementations did so.

    Allowing other types is listed as a common extension.

    C99 added _Bool bit-fields, and explicitly allowed "some other implementation-defined type". C23 allows bit-fields of bit-precise
    integer types; I'll avoid thinking about that for now.

    Implementions commonly use the declared type of a bit-field to
    affect the layout, not necessarily of the bit-field itself, but
    of the containing structure. Given that the standard doesn't
    require support for types other than bool and the int types (and
    now bit-precise integer types), the idea that `short bf:1` and
    `long bf:1` have different semantics is not, as far as I can tell,
    implied by anything in the standard.

    They may have different semantics, depending on what particular implementation-specific choices are made, but as best I can tell
    the C standard doesn't require them to.

    I understand that implementations *can* allow other integer types
    in bit-field declarations, and that they can use the declared type
    in implementation-defined ways.

    I don't think there is a specific explicit requirement that how a
    bit-field's declared type affects layout be implementation-defined.
    There is a general requirement that the number, order, and encodings
    of bytes that make up an object be implementation-defined if they
    are not explicitly specified.

    One possible approach would be to use the declared type only to
    determine the signedness of the bit-field (and its conversion
    behavior in the case of bool), and the upper bound for the number
    of bits (`int bf:33` is a constraint violation if int is 32 bits).
    In this relatively simple approach, there's no point in defining
    a bit-field with one of the char or short types.

    I think that depends on how the allocatable storage unit for a
    given bit-field is chosen. The C standard is pretty vague about
    what determines what allocatable storage unit is chosen in each
    case, and under what circumstances that might vary from bit-field
    to bit-field.

    Using gcc on Linux, if I define a 1-bit bit-field with a 64-bit type,
    that forces the containing structure to be at least 64 bits -- but
    not by reserving a 64-bit region to hold the bit-field. If I define
    a struct containing a 1-bit unsigned long long bit-field followed by
    a 1-byte ordinary member, the second member is at a 1-bytes offset.

    I had gotten the impression that the behavior is imposed by ABIs,
    but my copy of the "System V Application Binary Interface AMD64
    Architecture Processor Supplement" just says:

    - bit-fields are allocated from right to left

    I think that means they are allocated in order of low-to-high,
    because the AMD64 architecture is little-endian.

    - bit-fields must be contained in a storage unit appropriate for
    its declared type
    - bit-fields may share a storage unit with other struct / union
    members

    which doesn't seem to be enough to specify the behavior I see
    (and I find it annoyingly vague).

    Is there a document (ABI, compiler document, whatever) that specifies
    the (odd, to me) behavior I'm seeing?

    As best I can tell the layout you are seeing is consistent with
    the rules stated above. Perhaps the rules are deliberately meant
    to be an under-specification (which IMO is not a bad thing).

    Here's a test program:

    #include <stdio.h>
    #include <stddef.h>
    int main(void) {
    struct s1 { unsigned char bf:1; unsigned char c; };
    struct s2 { unsigned short bf:1; unsigned char c; };
    struct s3 { unsigned int bf:1; unsigned char c; };
    struct s4 { unsigned long bf:1; unsigned char c; };
    struct s5 { unsigned long long bf:1; unsigned char c; };

    printf("%-18s %-4s %-6s %s\n",
    "type", "size", "offset", "struct-size");

    printf("%-18s %-4zu %-6zu %-1zu\n",
    "unsigned char",
    sizeof (unsigned char),
    offsetof(struct s1, c),
    sizeof (struct s1));
    printf("%-18s %-4zu %-6zu %-1zu\n",
    "unsigned short",
    sizeof (unsigned short),
    offsetof(struct s2, c),
    sizeof (struct s2));
    printf("%-18s %-4zu %-6zu %-1zu\n",
    "unsigned int",
    sizeof (unsigned int),
    offsetof(struct s3, c),
    sizeof (struct s3));
    printf("%-18s %-4zu %-6zu %-1zu\n",
    "unsigned long",
    sizeof (unsigned long),
    offsetof(struct s4, c),
    sizeof (struct s4));
    printf("%-18s %-4zu %-6zu %-1zu\n",
    "unsigned long long",
    sizeof (unsigned long long),
    offsetof(struct s5, c),
    sizeof (struct s5));
    }

    and its output on my system (Ubuntu, x86_64):

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 4
    unsigned long 8 1 8
    unsigned long long 8 1 8

    Again, the declared type of a bit-field doesn't affect how the
    bit-field itself is allocated, but it does affect the size of the
    containing struct, but it doesn't prevent other members from being
    allocated within that space.

    You should be able to determine the layout exactly from the
    implementation's documentation. If you can't, that means the
    implementation is not conforming, because these are implemenation
    defined behaviors, and so much be documented.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Fri Aug 14 22:30:25 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    It would make a lot more sense *to me* if an N-bit bit field were
    simply N bits.

    [...]

    <snip>

    I had gotten the impression that the behavior is imposed by ABIs,
    but my copy of the "System V Application Binary Interface AMD64
    Architecture Processor Supplement" just says:

    - bit-fields are allocated from right to left

    I think that means they are allocated in order of low-to-high,
    because the AMD64 architecture is little-endian.

    - bit-fields must be contained in a storage unit appropriate for
    its declared type
    - bit-fields may share a storage unit with other struct / union
    members

    which doesn't seem to be enough to specify the behavior I see
    (and I find it annoyingly vague).

    Is there a document (ABI, compiler document, whatever) that specifies
    the (odd, to me) behavior I'm seeing?

    As best I can tell the layout you are seeing is consistent with
    the rules stated above. Perhaps the rules are deliberately meant
    to be an under-specification (which IMO is not a bad thing).

    Here's a test program:

    #include <stdio.h>
    #include <stddef.h>
    int main(void) {
    struct s1 { unsigned char bf:1; unsigned char c; };
    struct s2 { unsigned short bf:1; unsigned char c; };
    struct s3 { unsigned int bf:1; unsigned char c; };
    struct s4 { unsigned long bf:1; unsigned char c; };
    struct s5 { unsigned long long bf:1; unsigned char c; };

    FWIW, adding GCC's __attribute__((packed)) to each of those, we see:

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 4
    unsigned long 8 1 8
    unsigned long long 8 1 8

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 2
    unsigned long 8 1 2
    unsigned long long 8 1 2

    Which doesn't seem to violate the ABI rules you quoted.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sat Aug 15 16:10:30 2026
    From Newsgroup: comp.lang.c

    scott@slp53.sl.home (Scott Lurndal) writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    It would make a lot more sense *to me* if an N-bit bit field were
    simply N bits.

    [...]

    <snip>

    I had gotten the impression that the behavior is imposed by ABIs,
    but my copy of the "System V Application Binary Interface AMD64
    Architecture Processor Supplement" just says:

    - bit-fields are allocated from right to left

    I think that means they are allocated in order of low-to-high,
    because the AMD64 architecture is little-endian.

    - bit-fields must be contained in a storage unit appropriate for
    its declared type
    - bit-fields may share a storage unit with other struct / union
    members

    which doesn't seem to be enough to specify the behavior I see
    (and I find it annoyingly vague).

    Is there a document (ABI, compiler document, whatever) that specifies
    the (odd, to me) behavior I'm seeing?

    As best I can tell the layout you are seeing is consistent with
    the rules stated above. Perhaps the rules are deliberately meant
    to be an under-specification (which IMO is not a bad thing).

    Here's a test program:

    #include <stdio.h>
    #include <stddef.h>
    int main(void) {
    struct s1 { unsigned char bf:1; unsigned char c; };
    struct s2 { unsigned short bf:1; unsigned char c; };
    struct s3 { unsigned int bf:1; unsigned char c; };
    struct s4 { unsigned long bf:1; unsigned char c; };
    struct s5 { unsigned long long bf:1; unsigned char c; };

    FWIW, adding GCC's __attribute__((packed)) to each of those, we see:

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 4
    unsigned long 8 1 8
    unsigned long long 8 1 8

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 2
    unsigned long 8 1 2
    unsigned long long 8 1 2

    Which doesn't seem to violate the ABI rules you quoted.

    That's good to know I guess, although I'm not sure what it
    tells me. My impression is that using attribute__((packed))
    produces code that may be less portable than not using it.
    Generally I try to write code that avoids compiler-specific
    constructs whenever feasible.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Sun Aug 16 15:20:47 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    scott@slp53.sl.home (Scott Lurndal) writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Here's a test program:

    #include <stdio.h>
    #include <stddef.h>
    int main(void) {
    struct s1 { unsigned char bf:1; unsigned char c; };
    struct s2 { unsigned short bf:1; unsigned char c; };
    struct s3 { unsigned int bf:1; unsigned char c; };
    struct s4 { unsigned long bf:1; unsigned char c; };
    struct s5 { unsigned long long bf:1; unsigned char c; };

    FWIW, adding GCC's __attribute__((packed)) to each of those, we see:

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 4
    unsigned long 8 1 8
    unsigned long long 8 1 8

    type size offset struct-size
    unsigned char 1 1 2
    unsigned short 2 1 2
    unsigned int 4 1 2
    unsigned long 8 1 2
    unsigned long long 8 1 2

    Which doesn't seem to violate the ABI rules you quoted.

    That's good to know I guess, although I'm not sure what it
    tells me. My impression is that using attribute__((packed))
    produces code that may be less portable than not using it.
    Generally I try to write code that avoids compiler-specific
    constructs whenever feasible.

    I generally only use the packed attribute when creating
    a C struct to match a hardware register, data structure or
    data packet.

    --- Synchronet 3.22a-Linux NewsLink 1.2