• Re: Microcontroller software stacks (was Re: this girl calls c ugly)

    From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sun Jun 21 14:13:23 2026
    From Newsgroup: comp.lang.c

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

    One might also define data structures for control and status
    registers using bitfield structs.

    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.

    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };

    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?

    (Personal note: I tried sending an email to you at the address in
    your news posting, but my mailer complained about the address. If
    it's okay could I ask you to send me an email at the address in my
    news posting? Whatever you decide, thanks.)
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Mon Jun 22 08:58:02 2026
    From Newsgroup: comp.lang.c

    On 21/06/2026 23:13, Tim Rentsch wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:

    One might also define data structures for control and status
    registers using bitfield structs.

    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.

    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };

    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?


    Size-specific types are almost always the best choice for situations
    like this.

    When you are using bitfields simply as a way to pack small bits of data
    more efficiently, you use whatever style of type fits best with your
    needs - consistency with the rest of the code, making the sizes
    independent of the target, making the sizes adjust according to the
    target, maximal portability across compilers and standards version -
    whatever you like.

    But when you are using them to fit to an existing externally defined structure, fixed-size types are a big advantage (for the whole struct,
    not just the bitfields). It is easier to see that the structure is
    correct because you are explicit about the sizes. Types like "uint32_t"
    have the advantage that they are not portable to targets that can't
    support them - as it is likely that you would need to write such code
    somewhat differently for it to work on a machine that does not have such types, causing a compile-time error is useful.

    And when the structures represent hardware registers, such as here, you
    have additional motivation - these registers are typically accessed with volatile accesses, and you often want to be sure of the exact size of
    the accesses. That is always up to the implementation, but the norm is
    that when your bitfields are of a given size, generated volatile
    accesses for them use that matching size.

    So "uint32_t" says /precisely/ what the code author wants to say for the
    type. "unsigned" does not. "uint32_t" is appropriate regardless of the target and the choice of standard integer sizes - "unsigned" is not.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon Jun 22 03:35:24 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    On 21/06/2026 23:13, Tim Rentsch wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:
    One might also define data structures for control and status
    registers using bitfield structs.
    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.
    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };
    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?


    Size-specific types are almost always the best choice for situations
    like this.

    When you are using bitfields simply as a way to pack small bits of
    data more efficiently, you use whatever style of type fits best with
    your needs - consistency with the rest of the code, making the sizes independent of the target, making the sizes adjust according to the
    target, maximal portability across compilers and standards version -
    whatever you like.

    But when you are using them to fit to an existing externally defined structure, fixed-size types are a big advantage (for the whole struct,
    not just the bitfields). It is easier to see that the structure is
    correct because you are explicit about the sizes. Types like
    "uint32_t" have the advantage that they are not portable to targets
    that can't support them - as it is likely that you would need to write
    such code somewhat differently for it to work on a machine that does
    not have such types, causing a compile-time error is useful.

    And when the structures represent hardware registers, such as here,
    you have additional motivation - these registers are typically
    accessed with volatile accesses, and you often want to be sure of the
    exact size of the accesses. That is always up to the implementation,
    but the norm is that when your bitfields are of a given size,
    generated volatile accesses for them use that matching size.

    So "uint32_t" says /precisely/ what the code author wants to say for
    the type. "unsigned" does not. "uint32_t" is appropriate regardless
    of the target and the choice of standard integer sizes - "unsigned" is
    not.

    uint32_t x;
    says precisely that x is 32 bits, unsigned, with no padding bits. 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.

    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.

    (And of course int, signed int, unsigned int, and bool are the only
    portable types for bitfields -- but if you're using bit fields, it's
    likely that portability isn't your only priority.)
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From cross@cross@spitfire.i.gajendra.net (Dan Cross) to comp.lang.c on Mon Jun 22 10:45:06 2026
    From Newsgroup: comp.lang.c

    In article <86h5mv8umk.fsf@linuxsc.com>,
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:

    One might also define data structures for control and status
    registers using bitfield structs.

    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.

    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };

    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?

    No. There are issues of alignment and padding one must consider
    when using bitfields to model hardware registers, particularly
    if (say) a device driver is meant to be shared across ISAs.

    Using the exact width types really does make a difference; it's
    IB what those properties are, though we're usually at the mercy
    of the target platform's ABI anyway at that point.

    - Dan C.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From cross@cross@spitfire.i.gajendra.net (Dan Cross) to comp.lang.c on Mon Jun 22 10:50:10 2026
    From Newsgroup: comp.lang.c

    In article <111b35d$1duuq$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [snip]
    uint32_t x;
    says precisely that x is 32 bits, unsigned, with no padding bits. 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.

    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.

    If dealing with, e.g., hardware, then the author should probably
    constrain things so that bitfields occupy the fully width of the
    underlying type. E.g.,

    uint64_t bt:1;
    uint64_t reserved:63;

    And so forth.

    (And of course int, signed int, unsigned int, and bool are the only
    portable types for bitfields -- but if you're using bit fields, it's
    likely that portability isn't your only priority.)

    It may be, but you'll be programming against an ABI (or set of
    ABIs) or similar external standards that give you stronger
    guarantees than ISO C, at that point.

    - Dan C.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Mon Jun 22 12:59:27 2026
    From Newsgroup: comp.lang.c

    On 22/06/2026 12:35, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 21/06/2026 23:13, Tim Rentsch wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:
    One might also define data structures for control and status
    registers using bitfield structs.
    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.
    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */ >>>> uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */ >>>> uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */ >>>> uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */ >>>> #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };
    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?


    Size-specific types are almost always the best choice for situations
    like this.

    When you are using bitfields simply as a way to pack small bits of
    data more efficiently, you use whatever style of type fits best with
    your needs - consistency with the rest of the code, making the sizes
    independent of the target, making the sizes adjust according to the
    target, maximal portability across compilers and standards version -
    whatever you like.

    But when you are using them to fit to an existing externally defined
    structure, fixed-size types are a big advantage (for the whole struct,
    not just the bitfields). It is easier to see that the structure is
    correct because you are explicit about the sizes. Types like
    "uint32_t" have the advantage that they are not portable to targets
    that can't support them - as it is likely that you would need to write
    such code somewhat differently for it to work on a machine that does
    not have such types, causing a compile-time error is useful.

    And when the structures represent hardware registers, such as here,
    you have additional motivation - these registers are typically
    accessed with volatile accesses, and you often want to be sure of the
    exact size of the accesses. That is always up to the implementation,
    but the norm is that when your bitfields are of a given size,
    generated volatile accesses for them use that matching size.

    So "uint32_t" says /precisely/ what the code author wants to say for
    the type. "unsigned" does not. "uint32_t" is appropriate regardless
    of the target and the choice of standard integer sizes - "unsigned" is
    not.

    uint32_t x;
    says precisely that x is 32 bits, unsigned, with no padding bits. 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.

    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.

    There is sense in that, yes, but as I said the access type is important
    too. The struct Scott gave would not be the same if it used uint8_t
    instead of uint32_t for the bit-fields, even though there would be no difference in the alignments or paddings (on a "normal" cpus, rather
    than a DS9000). For hardware registers, access size is often critical -
    it is not like accessing ram. And while the choice of access size is implementation defined, the size of the type used for the bit-field is
    the most common way to determine that (for volatile accesses).

    If C had a different way of specifying access sizes, then it might be a
    bit different - perhaps _BitInt types would be the best choices for
    bit-field types.


    (And of course int, signed int, unsigned int, and bool are the only
    portable types for bitfields -- but if you're using bit fields, it's
    likely that portability isn't your only priority.)


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Mon Jun 22 15:04:52 2026
    From Newsgroup: comp.lang.c

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

    One might also define data structures for control and status
    registers using bitfield structs.

    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.

    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };

    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?

    The SATA hardware register is defined as a 32-bit register in the
    SATA specification. Therefore we explicitly declare it as such.

    There are other hardware registers in our implementation of the SATA
    controller that are defined as 64-bit registers, for those we use
    uint64_t (rather than relying on 'unsigned long' for 64-bit linux
    or 'unsigned long long' for 32-bit OS - and this code was designed
    to be compiled for both 32-bit and 64-bit targets originally).


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Mon Jun 22 15:23:40 2026
    From Newsgroup: comp.lang.c

    cross@spitfire.i.gajendra.net (Dan Cross) writes:
    In article <86h5mv8umk.fsf@linuxsc.com>,
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:

    One might also define data structures for control and status
    registers using bitfield structs.

    Yeah. This kind of application (among others) I consider one of
    the motivating forces behind bitfields.

    [Some whitespace trimming done in the excerpt below.]

    e.g. for the SATA UAHC_GLB_OOBR register:

    union UAHC_GBL_OOBR {
    uint32_t u;
    struct UAHC_GBL_OOBR_s {
    #if __BYTE_ORDER == __BIG_ENDIAN
    uint32_t we : 1; /**< R/W/H - Write enable. */
    uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
    uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
    uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
    uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
    #else
    uint32_t cimax : 8;
    uint32_t cimin : 8;
    uint32_t cwmax : 8;
    uint32_t cwmin : 7;
    uint32_t we : 1;
    #endif
    } s;
    };

    To me it seems kind of goofy to use uint32_t for the bitfields type.
    I would just use unsigned, which is just as sure to work as intended,
    isn't it?

    No. There are issues of alignment and padding one must consider
    when using bitfields to model hardware registers, particularly
    if (say) a device driver is meant to be shared across ISAs.

    That's a good choice of verb (model).

    As it happens, the primary use of this data structure is not
    to handle direct accesses to the hardware registers, but rather
    to model them in a simulation. So when the simulated CPU
    accesses the register, after determining the target address
    is assigned to the SATA controller GBL_OOB register, the
    SATA device model code (which hosts the register) will access
    the bitfields individually by name when implementing the
    semantics of a store to that register by the simulated CPU
    (which will typically be running the linux SATA driver).

    Far more maintainable and readable than manipulating the bit fields
    with shift and mask operations.

    e.g.

    if (gbl_oobr.s.we) { /* Writes are enabled */
    /* do it */
    }

    is better in all respects than

    if (gbl_oobr & 1) /* LE */
    or
    if (gbl_oobr & (1 << 31)) /* BE */
    or even
    if (gbl_oobr & (1 << WRITE_ENABLE_BIT_OFFSET))


    Of course the data structure can also be used by a real
    hardware device driver, with the caveat that the contents
    of the hardware register is loaded explicitly into the '.u'
    member by the driver before accessing the bitfields.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon Jun 22 13:02:27 2026
    From Newsgroup: comp.lang.c

    scott@slp53.sl.home (Scott Lurndal) writes:
    [...]
    There are other hardware registers in our implementation of the SATA controller that are defined as 64-bit registers, for those we use
    uint64_t (rather than relying on 'unsigned long' for 64-bit linux
    or 'unsigned long long' for 32-bit OS - and this code was designed
    to be compiled for both 32-bit and 64-bit targets originally).

    You could have used unsigned long long for both. I agree that using
    uint64_t is better if you specifically need 64 bits.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sun Jun 28 09:42:15 2026
    From Newsgroup: comp.lang.c

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

    [...]

    uint32_t x;
    says precisely that x is 32 bits, unsigned, with no padding bits.

    Actually it says a little bit more, but never mind that.

    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 would say this differently. The two member declarations shown
    might be meaningfully different, depending on the implementation:
    they >can< be different, but they don't have to be, and indeed on
    many implementations they are exactly the same.

    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.

    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. 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.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sun Jun 28 18:06:31 2026
    From Newsgroup: comp.lang.c

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

    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.

    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".
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Sun Jun 28 20:20:43 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:
    [...]
    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".

    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.

    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.

    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.

    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.

    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
    - 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?

    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.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2