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 <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 <Keith.S.Thompson+u@gmail.com> writes:<snip>
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.
[...]
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; };
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
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.
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.
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 74 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 47:44:41 |
| Calls: | 1,100 |
| Files: | 1,339 |
| Messages: | 275,630 |