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

    From Tim Rentsch@3:633/10 to All on Fri Aug 14 14:19:32 2026
    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.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Fri Aug 14 14:56:00 2026
    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.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Fri Aug 14 22:30:25 2026
    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.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Sat Aug 15 16:10:30 2026
    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.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Sun Aug 16 15:20:47 2026
    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.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Waldek Hebisch@3:633/10 to All on Wed Aug 19 19:34:35 2026
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    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:
    [...]
    <snip>
    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:
    <snip>
    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.

    I leave it to language and standard lawyers to discuss if such
    behaviour is mandated, but I find results of the test program
    obvious given how gcc behaves is general. First, IIUC given
    bit field should be at fixed bit offset within its storage unit.
    Second, storage units are allocated at byte addresses with
    appropriate alignment. Third, the passage above says that
    storage unit must be approptiate for its type, so 1 byte in
    case of unsigned char, 2 bytes in case of unsigend short,
    4 bytes in case of unsigned int, 8 bytes in case of unsignend
    long. Together, size of storage unit, its alignment and
    fixed bit offset of bit field within storage unit determines
    mininal size of structure needed to hold the bitfield.
    When this size is bigger then 1, then there is enough space
    within the storage unit to place the extra character there.
    When type is unsigned char, then size is 1 and there are
    no space to have bit field and char withing a single storage
    unit, so second one is allocated giving total size of the
    struct as 2. So in each case we get the struct size above.

    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    --
    Waldek Hebisch

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Wed Aug 19 15:26:14 2026
    antispam@fricas.org (Waldek Hebisch) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [...]
    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?

    [...]

    I leave it to language and standard lawyers to discuss if such
    behaviour is mandated, but I find results of the test program
    obvious given how gcc behaves is general. First, IIUC given
    bit field should be at fixed bit offset within its storage unit.
    Second, storage units are allocated at byte addresses with
    appropriate alignment. Third, the passage above says that
    storage unit must be approptiate for its type, so 1 byte in
    case of unsigned char, 2 bytes in case of unsigend short,
    4 bytes in case of unsigned int, 8 bytes in case of unsignend
    long.

    Thank you, that's the point I had overlooked (even though I
    quoted it).

    The ABI document's statement that "bit-fields must be contained in
    a storage unit appropriate for its declared type", can reasonably
    be read to mean that an unsigned char bit-field is contained in a
    1-byte storage unit, and an unsigned long long bit-field is contained
    in an 8-byte storage unit (given that unsigned long long is 8 bytes).

    I still find the language vague (and ungrammatical). What exactly
    does "appropriate" mean? Could an implementation decide that a
    32-bit storage unit is "appropriate" for an unsigned short bit-field? Presumably not, since that would break binary compatibility, but
    I don't see that it would violate the wording of the ABI.

    The C standard's language is also vague, but it's not trying to
    require binary compatibility.

    An implementation may allocate any addressable storage unit
    large enough to hold a bit-field. If enough space remains,
    a bit-field that immediately follows another bit-field in a
    structure shall be packed into adjacent bits of the same unit.

    [...]

    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51628> <https://stackoverflow.com/q/8568432/827263> <https://stackoverflow.com/a/8568441/827263>

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Aug 20 11:00:54 2026
    On 20/08/2026 00:26, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [...]
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. Passing the address of such a member to a function that assumes the pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51628> <https://stackoverflow.com/q/8568432/827263> <https://stackoverflow.com/a/8568441/827263>



    gcc "packed" can cause problems only if you don't use the compiler
    correctly (or if there are bugs in the compiler, as happens
    occasionally. gcc's bugzilla seems to be having trouble, so I have not checked your bug link).

    As you say, "packed" means that struct fields can be misaligned. This
    has two potential problems.

    One is that if you take the address of a field, you then have a pointer
    whose value is invalid for dereferencing as that type - so dereferencing
    it is UB. (You can still convert it to a character pointer and
    dereference that.) A compiler could therefore assume that if you have
    "int * p;" and you dereference it, then "p" must be correctly aligned.
    AFAIUI gcc does not make such assumptions, precisely because misaligned pointers do turn up in some code (either from the use of "packed" or by
    other means), and it is not an optimisation that is likely to be useful
    in any but the most obscure niche cases. (And if you have code in that category, you can always use __builtin_assume_aligned() to give the
    compiler the additional information.)

    The real problem with misaligned data is that on some systems, accessing misaligned data is not supported by the hardware. Such hardware
    certainly exists. From the links you gave, it appears to apply to
    SPARCs. It certainly applies to some of the smaller and cheaper ARM
    Cortex-M cores (M0, M1, M23 - perhaps more). And even on bigger
    devices, there can particular instructions that require correct
    alignment, such as "move multiple registers" or "load/store double
    register". In the x86 world, I believe there are some SIMD load/store instructions that must use aligned addresses. For most processors, and
    most instructions, misaligned accesses work but are a little slower than aligned accesses.

    If a misaligned access is not supported, the effects vary by device.
    Some will trigger a hardware fault and a program crash. Some will have
    a hardware fault and then software emulation of the misaligned access -
    then it will "work", but be /massively/ slower. And for some you simply
    get the wrong access - your program silently does the wrong thing. (On
    the msp430, trying to access a 16-bit value at an odd address had - in
    my brief testing long ago - the effect of accessing the data at one bye
    lower address but with swapped endianness. This is not a documented
    effect, however, so not something to rely on.)

    gcc is smart enough to use smaller accesses when it knows it is
    necessary. Accessing a misaligned 32-bit field when targeting a
    Cortex-M4 will give normal load/store 32-bit instructions - when
    targeting an M23, it will generate multiple 8-bit or 16-bit load/stores.
    And if you take a pointer to a packed field, you get a warning. So I
    think you have to put a bit of effort into getting in trouble here - you
    have to use pointers and ignore warnings, or use inappropriate
    command-line switches or generate code targeting one processor and try
    to run it on a different processor.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thu Aug 20 03:30:46 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 20/08/2026 00:26, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [...]
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.
    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address.
    Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)
    <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51628>
    <https://stackoverflow.com/q/8568432/827263>
    <https://stackoverflow.com/a/8568441/827263>

    gcc "packed" can cause problems only if you don't use the compiler
    correctly (or if there are bugs in the compiler, as happens
    occasionally. gcc's bugzilla seems to be having trouble, so I have
    not checked your bug link).

    That may be the case now, but it was definitely causing runtime bus
    errors on some systems when I reported it. It was later marked as
    FIXED.

    As you say, "packed" means that struct fields can be misaligned. This
    has two potential problems.

    One is that if you take the address of a field, you then have a
    pointer whose value is invalid for dereferencing as that type - so dereferencing it is UB. (You can still convert it to a character
    pointer and dereference that.) A compiler could therefore assume that
    if you have "int * p;" and you dereference it, then "p" must be
    correctly aligned. AFAIUI gcc does not make such assumptions,
    precisely because misaligned pointers do turn up in some code (either
    from the use of "packed" or by other means), and it is not an
    optimisation that is likely to be useful in any but the most obscure
    niche cases. (And if you have code in that category, you can always
    use __builtin_assume_aligned() to give the compiler the additional information.)

    I'm not sure what assumptions gcc makes now. Once you add something
    outside the sco of the standard like `__attribute__((packed))`, the
    behavior is arguably undefined anyway. I can't reproduce the original
    symptom with a modern gcc, but that may be because I don't have access
    to a system that traps on unaligned accesses.

    [...]

    gcc is smart enough to use smaller accesses when it knows it is
    necessary. Accessing a misaligned 32-bit field when targeting a
    Cortex-M4 will give normal load/store 32-bit instructions - when
    targeting an M23, it will generate multiple 8-bit or 16-bit
    load/stores. And if you take a pointer to a packed field, you get a warning. So I think you have to put a bit of effort into getting in
    trouble here - you have to use pointers and ignore warnings, or use inappropriate command-line switches or generate code targeting one
    processor and try to run it on a different processor.

    The problem is that if you have a separately compiled function like:

    void inc(int *p) {
    (*p)++;
    }

    the compiler has no way to know whether smaller (and perhaps
    significantly more expensive) accesses are necessary. Direct access to
    a packed member is easy enough, but once you take its address and pass
    it somewhere, there's no good way to deal with it.

    gcc added a warning option -Waddress-of-packed-member.

    I haven't been able to get any code to run on SPARC via godbolt.org.

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Aug 20 13:58:40 2026
    On 20/08/2026 12:30, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    On 20/08/2026 00:26, Keith Thompson wrote:
    antispam@fricas.org (Waldek Hebisch) writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    [...]
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.
    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. >>> Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)
    <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51628>
    <https://stackoverflow.com/q/8568432/827263>
    <https://stackoverflow.com/a/8568441/827263>

    gcc "packed" can cause problems only if you don't use the compiler
    correctly (or if there are bugs in the compiler, as happens
    occasionally. gcc's bugzilla seems to be having trouble, so I have
    not checked your bug link).

    That may be the case now, but it was definitely causing runtime bus
    errors on some systems when I reported it. It was later marked as
    FIXED.


    As I say, I can't seem to get any response from the gcc bugzilla server,
    so I can't look at the link for now. Certainly gcc has bugs, some of
    which can be serious.

    As you say, "packed" means that struct fields can be misaligned. This
    has two potential problems.

    One is that if you take the address of a field, you then have a
    pointer whose value is invalid for dereferencing as that type - so
    dereferencing it is UB. (You can still convert it to a character
    pointer and dereference that.) A compiler could therefore assume that
    if you have "int * p;" and you dereference it, then "p" must be
    correctly aligned. AFAIUI gcc does not make such assumptions,
    precisely because misaligned pointers do turn up in some code (either
    from the use of "packed" or by other means), and it is not an
    optimisation that is likely to be useful in any but the most obscure
    niche cases. (And if you have code in that category, you can always
    use __builtin_assume_aligned() to give the compiler the additional
    information.)

    I'm not sure what assumptions gcc makes now. Once you add something
    outside the sco of the standard like `__attribute__((packed))`, the
    behavior is arguably undefined anyway. I can't reproduce the original symptom with a modern gcc, but that may be because I don't have access
    to a system that traps on unaligned accesses.


    You can use godbolt to generate the code. If you are targetting a
    processor that does not support misaligned accesses, it should generate smaller accesses for misaligned packed data. With the following link,
    you can try changing the "-mcpu=cortex-23" to "cortex-m33" and see the difference in the code.

    <https://godbolt.org/z/PqGabdfWG>

    If you can make an example where a modern gcc for the target hardware generates multiple small accesses, and an older version generates a
    misaligned big access, then that would probably show the issue even
    without access to the hardware.

    [...]

    gcc is smart enough to use smaller accesses when it knows it is
    necessary. Accessing a misaligned 32-bit field when targeting a
    Cortex-M4 will give normal load/store 32-bit instructions - when
    targeting an M23, it will generate multiple 8-bit or 16-bit
    load/stores. And if you take a pointer to a packed field, you get a
    warning. So I think you have to put a bit of effort into getting in
    trouble here - you have to use pointers and ignore warnings, or use
    inappropriate command-line switches or generate code targeting one
    processor and try to run it on a different processor.

    The problem is that if you have a separately compiled function like:

    void inc(int *p) {
    (*p)++;
    }

    the compiler has no way to know whether smaller (and perhaps
    significantly more expensive) accesses are necessary. Direct access to
    a packed member is easy enough, but once you take its address and pass
    it somewhere, there's no good way to deal with it.

    Yes. The best it can do is warn you when you take the address of the
    packed field.


    gcc added a warning option -Waddress-of-packed-member.

    Exactly. (It was added in gcc 9, as far as I can tell.)


    I haven't been able to get any code to run on SPARC via godbolt.org.


    I could not see incorrect code generated for SPARC gcc, but I am not
    very familiar with SPARC assembly, my example code may not have shown
    the problem, and godbolt only has back to gcc 12 for the SPARC. So my
    own tests are far from conclusive.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Thu Aug 20 14:50:25 2026
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    antispam@fricas.org (Waldek Hebisch) writes:
    <snip>
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. >Passing the address of such a member to a function that assumes the >pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    In modern systems, so long as the misaligned access is
    fully contained within a single cache line, there is
    zero additional latency for the misaligned access.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Thu Aug 20 22:15:31 2026
    On Wed, 19 Aug 2026 15:26:14 -0700
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)


    On all surviving general-purpose CPUs that I am aware of, not just x86.
    Not that there are many that survived.
    May be, some of Chinese general-purpose designs are exception.

    And in many cases it's not slower.
    Typically, mis-aligned accesses become slower than aligned only when
    dozens of such accesses executed in tight sequence.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Aug 20 12:31:15 2026
    On 8/20/2026 7:50 AM, Scott Lurndal wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    antispam@fricas.org (Waldek Hebisch) writes:
    <snip>
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address.
    Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    In modern systems, so long as the misaligned access is
    fully contained within a single cache line, there is
    zero additional latency for the misaligned access.


    fully contained within a single cache line, that's step 1. We also need
    to make sure the cache line is aligned on a cache line boundary.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Waldek Hebisch@3:633/10 to All on Thu Aug 20 19:55:19 2026
    Scott Lurndal <scott@slp53.sl.home> wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    antispam@fricas.org (Waldek Hebisch) writes:
    <snip>
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. >>Passing the address of such a member to a function that assumes the >>pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    In modern systems, so long as the misaligned access is
    fully contained within a single cache line, there is
    zero additional latency for the misaligned access.

    But most efficient access fetches the whole line. If it stays
    within a single cache line, then by neccessity it is aligned.

    And adjective "modern" can be applied to microcontrollers.
    Microcontrollers typically have word-sized data bus. Misaligned
    word sized access needs two transfers over data bus, clearly
    less efficient than single access.

    --
    Waldek Hebisch

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Thu Aug 20 13:37:47 2026
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    If you can make an example where a modern gcc for the target hardware generates multiple small accesses, and an older version generates a misaligned big access, then that would probably show the issue even
    without access to the hardware.

    Yes, but I lack both the familiarity with the relevant assembly
    languages and the patience to do that.

    [...]

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Aug 21 08:57:50 2026
    On 20/08/2026 21:55, Waldek Hebisch wrote:
    Scott Lurndal <scott@slp53.sl.home> wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    antispam@fricas.org (Waldek Hebisch) writes:
    <snip>
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. >>> Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    In modern systems, so long as the misaligned access is
    fully contained within a single cache line, there is
    zero additional latency for the misaligned access.

    But most efficient access fetches the whole line. If it stays
    within a single cache line, then by neccessity it is aligned.

    The fetch of the cache line from memory is aligned, but the fetch of the actual data from the cache to the processor may not be aligned.


    And adjective "modern" can be applied to microcontrollers.
    Microcontrollers typically have word-sized data bus. Misaligned
    word sized access needs two transfers over data bus, clearly
    less efficient than single access.


    Indeed. And for some microcontrollers - including some modern ones - misaligned accesses are simply not allowed by the hardware. Most microcontrollers, modern or not, do not have cache at all unless they
    are quite high-end.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Fri Aug 21 14:31:25 2026
    On Thu, 20 Aug 2026 11:00:54 +0200
    David Brown <david.brown@hesbynett.no> wrote:

    It certainly applies to some of the smaller and
    cheaper ARM Cortex-M cores (M0, M1, M23 - perhaps more).

    Are you sure about M1?
    If true, it is disappointing.

    I would not touch Cortex-M0 or M23 for many other reasons so can't
    be disappointed about them.

    OTOH, M1 is something that in theory I like.
    Never used it, because original licensing terms were absolute
    non-starter for sort of projects that we are specializing in (volumes
    rarely reaching 1000 units and never 10,000), but I am very likely to
    use it if we ever chose to work with Microsemi, on which licensing of
    M1 is much saner.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Aug 21 13:41:09 2026
    On 21/08/2026 13:31, Michael S wrote:
    On Thu, 20 Aug 2026 11:00:54 +0200
    David Brown <david.brown@hesbynett.no> wrote:

    It certainly applies to some of the smaller and
    cheaper ARM Cortex-M cores (M0, M1, M23 - perhaps more).

    Are you sure about M1?
    If true, it is disappointing.

    I haven't dug through the documentation, but gcc certainly thinks so -
    packed fields are accessed by byte rather than misaligned word accesses.
    It is possible, of course, that gcc is pessimistic here, or that
    misaligned accesses are an optional feature for the core.

    Since the M1 targets soft cores on programmable logic, and support for misaligned accesses takes extra logic (consuming space and possibly
    reducing maximum clock speed) for a feature that is rarely useful on
    embedded systems, it does not surprise me that the M1 does not support it.


    I would not touch Cortex-M0 or M23 for many other reasons so can't
    be disappointed about them.

    I have not used these cores either, but there is nothing inherently
    wrong with them - they are useful for small, cheap and low-power microcontrollers. I am sure there are aspects of these cores where I
    would have preferred a different choice of which features are missing (compared to their larger brother cores), but I expect that would always
    be true.


    OTOH, M1 is something that in theory I like.
    Never used it, because original licensing terms were absolute
    non-starter for sort of projects that we are specializing in (volumes
    rarely reaching 1000 units and never 10,000), but I am very likely to
    use it if we ever chose to work with Microsemi, on which licensing of
    M1 is much saner.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Michael S@3:633/10 to All on Fri Aug 21 14:58:48 2026
    On Fri, 21 Aug 2026 14:31:25 +0300
    Michael S <already5chosen@yahoo.com> wrote:


    OTOH, M1 is something that in theory I like.
    Never used it, because original licensing terms were absolute
    non-starter for sort of projects that we are specializing in (volumes
    rarely reaching 1000 units and never 10,000), but I am very likely to
    use it if we ever chose to work with Microsemi, on which licensing of
    M1 is much saner.


    The above should read "because original licensing terms on Altera and
    Xilinx devices were absolute non-starter".


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Fri Aug 21 14:59:58 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 20/08/2026 21:55, Waldek Hebisch wrote:
    Scott Lurndal <scott@slp53.sl.home> wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    antispam@fricas.org (Waldek Hebisch) writes:
    <snip>
    Other post mentioned gcc "packed". gcc "packed" does not
    respect alignment rules, so in such cases 2 byte are enough
    regardless of type.

    gcc "packed" can also cause quiet crashes on some systems. It can
    result in, for example, an int member being allocated at an odd address. >>>> Passing the address of such a member to a function that assumes the
    pointed-to object is correctly aligned can result in Bad Things
    Happening. (On x86, as I understand it, misaligned accesses
    are merely somewhat slower than aligned accesses.)

    In modern systems, so long as the misaligned access is
    fully contained within a single cache line, there is
    zero additional latency for the misaligned access.

    But most efficient access fetches the whole line. If it stays
    within a single cache line, then by neccessity it is aligned.

    The fetch of the cache line from memory is aligned, but the fetch of the >actual data from the cache to the processor may not be aligned.

    A barrel shifter will rotate the data appropriately in a single clock.

    Small microcontrollers often don't include a barrel shifter.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Fri Aug 21 15:41:10 2026
    David Brown <david.brown@hesbynett.no> writes:
    On 21/08/2026 13:31, Michael S wrote:
    On Thu, 20 Aug 2026 11:00:54 +0200
    David Brown <david.brown@hesbynett.no> wrote:

    It certainly applies to some of the smaller and
    cheaper ARM Cortex-M cores (M0, M1, M23 - perhaps more).

    Are you sure about M1?
    If true, it is disappointing.

    I haven't dug through the documentation, but gcc certainly thinks so - >packed fields are accessed by byte rather than misaligned word accesses.
    It is possible, of course, that gcc is pessimistic here, or that
    misaligned accesses are an optional feature for the core.

    Since the M1 targets soft cores on programmable logic, and support for >misaligned accesses takes extra logic (consuming space and possibly
    reducing maximum clock speed) for a feature that is rarely useful on >embedded systems, it does not surprise me that the M1 does not support it.

    The Cortex-M7 supports unaligned loads to normal (DRAM) memory, but not
    to device memory, with a peformance penalty.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Tim Rentsch@3:633/10 to All on Sun Aug 23 07:53:23 2026
    scott@slp53.sl.home (Scott Lurndal) writes:

    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.

    I would use the packed attribute if it were necessary to conform
    to some externally imposed layout, such as a hardware register or
    network packet. I don't know of any cases were it is necessary.
    Maybe the data formats you need to match are more exotic than
    ones I am used to. Can you give an example of a format where the
    format cannot be matched using only language features available
    in standard C?

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Aug 23 17:33:17 2026
    On 23/08/2026 16:53, Tim Rentsch wrote:
    scott@slp53.sl.home (Scott Lurndal) writes:

    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.

    I would use the packed attribute if it were necessary to conform
    to some externally imposed layout, such as a hardware register or
    network packet. I don't know of any cases were it is necessary.
    Maybe the data formats you need to match are more exotic than
    ones I am used to. Can you give an example of a format where the
    format cannot be matched using only language features available
    in standard C?

    Programmers do not - IME - use "packed" structs because it is the only
    way to deal with externally imposed layout. They use them because they
    are sometimes the simplest, clearest and most efficient methods. Since
    you can always access data using pointers to unsigned char, it is never
    the /only/ way to do things.

    The code I am currently working on deals with a specialised network
    interface chipset for power-line communication. The packets sent back
    and forth between the chipset and the microcontroller I am programming
    have different formats for the different messages types - some 30
    different types. Each format contains mixes of 8-bit, 16-bit, 32-bit
    and sometimes bigger fields, with no padding and no consideration for alignment.

    Now, there is no doubt that this could all be handled by receiving the incoming telegram into an array of unsigned char, pulling out the bytes individually, building up the fields with shifts and ors, then storing
    the results into a new struct that has the same fields with native
    padding and alignment. Alternatively, I could have types defined like "typedef struct { unsigned char v[4]; } packed_32_t;", and so on, and
    use these as fields of a struct that directly matched the format of the hardware telegram.

    But it is /far/ simpler and clearer to use a packed struct. It gives
    the most efficient resulting code, avoids unnecessary extra copies
    (saving clock cycles and ram space), is easy to check with the
    documentation, and easy to maintain.

    I think you would be very hard pushed to find a case where packed
    structs are the /only/ solution, but it's not hard to find cases where
    many programmers would consider them the /best/ solution. (Usually a
    certain degree of non-portability is fine in such systems.)




    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)