25

I'm studying the basics of the C language. I arrived at the chapter of structures with bit fields. The book shows an example of a struct with two different types of data: various bools and various unsigned ints.

The book declares that the structure has a size of 16 bits and that without using padding the structure would measure 10 bits.

This is the structure that the book uses in the example:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

int main(void)
{
       struct test Test;

       printf("%zu\n", sizeof(Test));

       return 0;
}

Why on my compiler instead does the exact same structure measure 16 bytes (rather than bits) with padding and 16 bytes without padding?

I'm using

GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit

This is result I'm getting:

enter image description here

Here is a picture of the page where the example is:

enter image description here

Boann
  • 48,794
  • 16
  • 117
  • 146
AlexQualcosa
  • 467
  • 6
  • 11
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/169865/discussion-on-question-by-alexqualcosa-c-inaccurate-size-of-a-struct-with-bit). – Samuel Liew Apr 26 '18 at 13:34
  • 4
    From what I remember there's some known bugs in GCC here. That being said, don't use bit-fields. Don't read a book teaching you to use bit-fields. – Lundin Apr 26 '18 at 14:47
  • 3
    The book seems like garbage. C doesn't "use unsigned int as the basic layout unit for structures with bit fields". Per **6.7.2.1 Structure and union specifiers**, paragraph 5 of [the C standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), a bit field can be any type: "A bit-field shall have a type that is a qualified or unqualified version of `_Bool`, `signed int`, `unsigned int`, **or some other implementation-defined type**. It is implementation-defined whether atomic types are permitted." – Andrew Henle Apr 26 '18 at 17:24

7 Answers7

30

The Microsoft ABI lays out bitfields in a different way than GCC normally does it on other platforms. You can choose to use the Microsoft-compatible layout with the -mms-bitfields option, or disable it with -mno-ms-bitfields. It's likely that your GCC version uses -mms-bitfields by default.

According to the documentation, When -mms-bitfields is enabled:

  • Every data object has an alignment requirement. The alignment requirement for all data except structures, unions, and arrays is either the size of the object or the current packing size (specified with either the aligned attribute or the pack pragma), whichever is less. For structures, unions, and arrays, the alignment requirement is the largest alignment requirement of its members. Every object is allocated an offset so that: offset % alignment_requirement == 0
  • Adjacent bit-fields are packed into the same 1-, 2-, or 4-byte allocation unit if the integral types are the same size and if the next bit-field fits into the current allocation unit without crossing the boundary imposed by the common alignment requirements of the bit-fields.

Since bool and unsigned int have different sizes, they are packed and aligned separately, which increases the struct size substantially. The alignment of unsigned int is 4 bytes, and having to realign three times in the middle of the struct leads to a 16 byte total size.

You can get the same behavior of the book by changing bool to unsigned int, or by specifying -mno-ms-bitfields (though this will mean you can't inter-operate with code compiled on Microsoft compilers).

Note that the C standard does not specify how bitfields are laid out. So what your book says may be true for some platforms but not for all of them.

interjay
  • 107,303
  • 21
  • 270
  • 254
  • 22
    This is a great answer overall but that final paragraph is the real key point; far too many books relate implementation specifics as broad fact without qualification. It drives me nuts... – Lightness Races in Orbit Apr 26 '18 at 14:35
  • In the last sentence, you might change "the standard" to "the C language standard," since, as you note, there are standards that do. – Owen Apr 26 '18 at 17:53
  • 2
    Although the C standard does not specify all the details of how bitfields are laid out, it does place *some* requirements (see [C11 6.7.2.1/11](http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p11)). There is a lot of wiggle room there, but as it applies to the OP's structure, the MS ABI requires a layout that cannot be interpreted as conforming to the C standard. Microsoft non-conformance strikes again! – John Bollinger Apr 27 '18 at 16:25
8

Taken as describing the provisions of the C language standard, your text makes unjustified claims. Specifically, not only does the standard not say that unsigned int is the basic layout unit of structures of any kind, it explicitly disclaims any requirement on the size of the storage units in which bitfield representations are stored:

An implementation may allocate any addressable storage unit large enough to hold a bit- field.

(C2011, 6.7.2.1/11)

The text also makes assumptions about padding that are not supported by the standard. A C implementation is free to include an arbitrary amount of padding after any or all elements and bitfield storage units of a struct, including the last. Implementations typically use this freedom to address data alignment considerations, but C does not limit padding to that use. This is altogether separate from the unnamed bitfields that your text refers to as "padding".

I guess the book should be commended, however, for avoiding the distressingly common misconception that C requires the declared data type of a bit field to have anything to do with the size of the storage unit(s) in which its representation resides. The standard makes no such association.

Why on my compiler instead does the exact same structure measure 16 bytes (rather than bits) with padding and 16 bytes without padding?

To cut the text as much slack as possible, it does distinguish between the number of bits of data occupied by members (16 bits total, 6 belonging to unnamed bitfields) and the overall size of instances of the struct. It seems to be asserting that the overall structure will be the size of an unsigned int, which apparently is 32 bits on the system it is describing, and that would be the same for both versions of the struct.

In principle, your observed sizes could be explained by your implementation using a 128-bit storage unit for the bitfields. In practice, it likely uses one or more smaller storage units, so that some of the extra space in each struct is attributable to implementation-provided padding, such as I touch on above.

It is very common for C implementations to impose a minimum size on all structure types, and therefore to pad representations to that size when necessary. Often this size matches the strictest alignment requirement of any data type supported by the system, though that's, again, an implementation consideration, not a requirement of the language.

Bottom line: only by relying on implementation details and / or extensions can you predict the exact size of a struct, regardless of the presence or absence of bitfield members.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
6

To my surprise there seems to be a difference between some GCC 4.9.2 online compilers. First, this is my code:

#include <stdio.h>
#include <stdbool.h>

struct test {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

struct test_packed {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
} __attribute__((packed));

int main(void)
{
       struct test padding;
       struct test_packed no_padding;

       printf("with padding: %zu bytes = %zu bits\n", sizeof(padding), sizeof(padding) * 8);
       printf("without padding: %zu bytes = %zu bits\n", sizeof(no_padding), sizeof(no_padding) * 8);

       return 0;
}

And now, results from different compilers.

GCC 4.9.2 from WandBox:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

GCC 4.9.2 from http://cpp.sh/:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

BUT

GCC 4.9.2 from theonlinecompiler.com:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

(to properly compile you need to chagne %zu to %u)

EDIT

@interjay's answer might explain this. When I added -mms-bitfields to GCC 4.9.2 from WandBox, I got this:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
Grzegorz Adam Kowalski
  • 5,243
  • 3
  • 29
  • 40
6

The C standard doesn't described all details about how variables shall be placed in memory. This leaves room for optimization that depends on the platform used.

To give your self an idea about how things are located in memory, you can do like this:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};


int main(void)
{
  struct test Test = {0};
  int i;
  printf("%zu\n", sizeof(Test));

  unsigned char* p;
  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.opaque = true;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.fill_color = 3;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  return 0;
}

Running this on ideone (https://ideone.com/wbR5tI) I get:

4
00000000
01000000
07000000

So I can see that opaque and fill_color are both in the first byte. Running exactly the same code on a Windows machine (using gcc) gives:

16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000

So here I can see that opaque and fill_color are not both in the first byte. It seems that opaque takes up 4 bytes.

This explains that you get 16 bytes in total, i.e. the bool takes 4 bytes each and then 4 bytes for the fields in between and after.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • To avoid undefined behavior, your format needs to match your argument types. In this case you need to match `unsigned char` with a directive of form `%02hhx` (note the "hh"). It looks like the observed behavior is *probably* what you intended anyway, but don't rely on luck. – John Bollinger Apr 26 '18 at 16:06
  • Note also that although C does not require the `opaque` and `fill_color` members to appear in the same byte, it *does* require them to appear in the same "addressible storage unit", however the implementation chooses that, if there is enough space. The observed 16-byte layout conforms only if the storage unit to which `opaque` is assigned is larger than four bytes or smaller than 4 bits. – John Bollinger Apr 26 '18 at 16:13
5

Before the author defines the struct he says he wants to divide the bit fields into two bytes so there will be one byte containing the bitfields for the fill-related information and one byte for the border related information.

To achieve that, he adds (inserts) a few unused bits (bitfield):

unsigned int       4;  // padding of the first byte

he also padds the second byte, but there is no need for that.

So before the padding there would be 10 bits in use and afer the padding there are 16 bits defined (but not al of them in use).


Note: the author uses bool to indicate a 1/0 field. The author next assumes the _Bool C99 type is aliased to bool. But it seems compilers get a bit confused here. Replacing bool with unsigned int would solve it. From C99:

6.3.2: The following may be used in an expression wherever an int or unsigned int may be used:

  • A bit-field of type _Bool, int, signed int, or unsigned int.
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • But OP claims that he got a size of 16 bytes, which to me seems very unlikely. – interjay Apr 26 '18 at 11:19
  • 1
    @interjay, indeed. Seems to me he mixes bits and bytes. – Paul Ogilvie Apr 26 '18 at 11:20
  • Possibly, but the whole question is about why he got bytes instead of bits. – interjay Apr 26 '18 at 11:22
  • Thanks for the answers, I updated the main thread by inserting a screen of the result obtained by the compiler. – AlexQualcosa Apr 26 '18 at 11:24
  • 1
    @interjay If `bool` and `unsigned` cause the compiler to align those fields, a result of 16 _bytes_ is not that strange. – chux - Reinstate Monica Apr 26 '18 at 11:24
  • 1
    @chux I guess it's possible, though it's very strange behavior to align fields in the middle of a bitfield. Especially when it isn't reproduced on Linux. Maybe it's something that GCC does on Windows in order to adhere to the platform ABI? – interjay Apr 26 '18 at 11:31
  • @interjay I think your idea is correct. Often a good reasons to sort bit field members out by type to insure low memory usage, even without "packed". – chux - Reinstate Monica Apr 26 '18 at 11:33
  • Conclusion: don't use bool. In C you don't need it. – joop Apr 26 '18 at 11:37
  • Thank you for answering, I do not know what you mean with the word "align", can you tell me a useful link to study this aspect of the bit fields? – AlexQualcosa Apr 26 '18 at 11:40
  • 1
    @joop `bool` is fine. Expecting nice packing with a bit-field using mixed types is the source of the difficulties. Bit-fields are higher mainatence, not `bool`. – chux - Reinstate Monica Apr 26 '18 at 11:45
  • @PaulOgilvie My apologies for comments getting OT for your answer. – chux - Reinstate Monica Apr 26 '18 at 11:46
  • Bool is not needed, and it confuses idiots. The smallest addressable unit is char, so bool cannot be smaller than char (do we *really* need a pointer to bool?). Using bool in a bitfield has zero gains, and it allowed Microsoft to screw up. – joop Apr 26 '18 at 11:50
  • @joop bool is not smaller than a char, it's 1 byte. I do not care if bool is necessary or not, I'm interested in understanding why the result does not match that of the book – AlexQualcosa Apr 26 '18 at 11:53
  • **1.** That's what I said. Bool has *no* purpose. **2.** Because (as @interjay concluded) the Microsoft ABI forces it to align to a char boundary (possibly for *easy* integration with other Microsoft *languages*) – joop Apr 26 '18 at 11:57
  • @PaulOgilvie, I'm not clear on what it is you claim compilers get confused about. If it's that they don't necessarily identify `bool` with `_Bool`, then perhaps you could explain why you recommend changing to `unsigned int` instead of to `_Bool`? Additionally, inasmuch as the OP's code `#include`s `stdbool.h`, are you saying that that might not be enough to resolve the confusion you're talking about? – John Bollinger Apr 26 '18 at 17:01
  • @JohnBollinger, I was under the impression that the use of `_Bool` resulted in the struct being much larger than expected. It could mean a compiler does not use proper bit fields when typed as `_Bool : 1;` – Paul Ogilvie Apr 26 '18 at 18:09
  • 1
    @joop: The semantics of a `Bool` bitfield are different from those of a single-bit `unsigned char` bitfield. Storing the value `2` into a single-bit field of any non-`Bool` unsigned type will clear that bit, while storing `2` into a single-bit `Bool` will set that bit. On the other hand, I think any code which tries to write anything other than 0 or 1 to a single-bit field should be regarded as highly suspect. If `x` might have a value other than 0 or 1, code wanting to store it to a bitfield should either be written as `thing.field = !!(x)` or `thing.field = (x & 1);` to make clear... – supercat Apr 26 '18 at 20:36
  • 1
    ...what behavior would be intended if `x` has some other value. Still, since the Standard says `foo.singleBitNonBool=x;` is equivalent to `foo.singleBitNonBool=(x & 1);` and `foo.singleBitBool=x;` is equivalent to `foo.singleBitBool=!!(x);`, code that simply uses `x` will be affected by the differences in semantics. – supercat Apr 26 '18 at 20:44
  • @PaulOgilvie, it is possible that use of a bitfield of type `_Bool` does cause the compiler to choose a different layout than it would use if the field were of a different type, but it is by no means clear that that has anything to do with the OP's observations. In any case, the premise that the structure should be any specific size is flawed. I cannot judge whether the particular layout chosen conforms, but a conforming compiler could certainly produce a 16-byte layout. There's no reason to suppose that it would have to be confused in some sense in order to do so. – John Bollinger Apr 27 '18 at 00:30
2

You are completely misinterpreting what the book says.

There are 16 bits worth of bit fields declared. 6 bits are unnamed fields that cannot be used for anything - that's the padding mentioned. 16 bits minus 6 bits equals 10 bits. Not counting the padding fields, the struct has 10 useful bits.

How many bytes the struct has, depends on the quality of the compiler. Apparently you ran into a compiler that doesn't pack bool bitfields in a struct, and it uses 4 bytes for a bool, some memory for bitfields, plus struct padding, total 4 bytes, another 4 bytes for a bool, more memory for bitfields, plus struct padding, total 4 bytes, adding up to 16 bytes. It's rather sad really. This struct could quite reasonably be two bytes.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
1

There have historically been two common ways of interpreting the types of bitfield elements:

  1. Examine whether the type is signed or unsigned, but ignore distinctions between "char", "short", "int", etc. in deciding where an element should be placed.

  2. Unless a bitfield is preceded by another with the same type, or the corresponding signed/unsigned type, allocate an object of that type and place the bitfield within it. Place following bitfields with the same type in that object if they fit.

I think the motivation behind #2 was that on a platform where 16-bit values need to be word-aligned, a compiler given something like:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 7;
};

might be able to allocate a two-byte structure, with both fields being placed in the second byte, but if the structure had been:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 15;
};

it would be necessary that all of f2 fit within a single 16-bit word, which would thus necessitate a padding byte before f1. Because of the Common Initial Sequence rule, f1 must be placed identically in those two structures, which would imply that if f1 could satisfy the Common Initial Sequence rule, it would need padding before it even in the first structure.

As it is, code which wants to allow the denser layout in the first case can say:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned char f2 : 7;
};

and invite the compiler to put both bitfields into a byte immediately following x. Since the type is specified as unsigned char, the compiler need not worry about the possibility of a 15-bit field. If the layout were:

struct foo {
  char x;  // Not a bitfield
  unsigned short f1 : 1;
  unsigned short f2 : 7;
};

and the intention was that f1 and f2 would sit in the same storage, then the compiler would need to place f1 in a way that could support a word-aligned access for its "bunkmate" f2. If the code were:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned short f2 : 15;
};

then f1 would be placed following x, and f2 in a word by itself.

Note that the C89 Standard added a syntax to force the layout that prevent f1 from being placed in a byte before the storage used f2:

struct foo {
  char x;  // Not a bitfield
  unsigned short : 0;  // Forces next bitfield to start at a "short" boundary
  unsigned short f1 : 1;
  unsigned short f2 : 15;
};

The addition of the :0 syntax in C89 largely eliminates the need to have compilers regard changing types as forcing alignment, except when processing old code.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Your approach (2) does not conform to any version of the C standard, so in what sense is it common? Are you referring to pre-standard C implementations (you do say "historically")? Or are you asserting that it is a characteristic common to many now-relevant C implementations to be non-conforming in this way? Or perhaps are you referring to a small number of implementations with a large installed base? – John Bollinger Apr 26 '18 at 17:13
  • @JohnBollinger: I don't have a K&R1 or K&R2 handy, but I think that's how it was defined there. It's also matches the behavior of some pre-standard compilers I've used; while nothing in the Standard would require such behavior, nothing would prohibit it either. – supercat Apr 26 '18 at 17:14
  • 1
    C89, C99, and C11 all say "If enough space remains [in the same addressable storage unit], a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit." I don't see any reasonable interpretation of "addressable storage unit" that affords approach (2) conforming to that. – John Bollinger Apr 26 '18 at 17:20
  • K&R1 does not have this language (it happens I do have a copy handy), but it couches the discussion of bit fields in terms of minimizing the amount of space used, which is contrary to (2). – John Bollinger Apr 26 '18 at 17:25
  • @JohnBollinger: The Standard is rather mushy here as to what the "same addressable storage unit" means in cases where a field's type doesn't match the previous one. Given `struct b2 { char x; unsigned m1:4,m2:8,m3:8,m4:4;};`, clang and gcc both yield a 4-byte structure. Into what type of addressable storage unit are m1..m4 placed? If one changes the declared type of `m3` and `m4` to `unsigned char`, how should that affect things? – supercat Apr 26 '18 at 18:00
  • @JohnBollinger: I think the behavior the OP is seeing is a result of a compiler applying principle #2. Based on experimentation, it seems clang and gcc have some interesting and quirky rules that look at a later field's type in deciding whether it "fits" in the same storage as the previous one, but I don't know the exact rules. – supercat Apr 26 '18 at 18:24
  • The observed behavior may indeed result from the compiler applying principle #2, but inasmuch as such behavior is non-conforming, I'm interested in how widespread it actually is. And it *is* non-conforming. The standard allows implementations much leeway in choosing how large an ASU is, but its specifications for whether bit-fields are assigned to the same or different ones leave no room for distinguishing based on declared type. – John Bollinger Apr 26 '18 at 19:01
  • @JohnBollinger: Both gcc and clang *do* distinguish based on declared type, but in somewhat surprising fashion. I think they look at whether each new object would straddle an 8, 16, or 32-bit boundary based upon the declared type of the new object, but without regard for the type of the preceding object. Thus, given `struct foo {char a; char b:7; int c:2; char d:7; char e;};` they decide that since `c` wouldn't straddle a 32-bit boundary it can fit in the same 32-bit word as what preceded it, even though what preceded it hadn't *been* part of a 32-bit word... – supercat Apr 26 '18 at 19:11
  • ...and in fact *still* wouldn't be [since operations on `b`, `c`, and `d` aren't allowed to touch the storage associated with `a`]. If `b` had been omitted, `c` and `d` would have been placed in separate bytes, because otherwise `d` would have straddled a byte boundary even though it would seem that `c` should have allocated a 32-bit storage location which should have 30 bits left that could hold `d`. – supercat Apr 26 '18 at 19:16
  • @JohnBollinger: IMHO, bitfields in their present form should be deprecated in favor of a syntax that unambiguously specifies how things should be laid out, with a proviso that compilers the range of supported layouts would be Implementation-Defined, but an implementations that accept a program using a certain layout must correctly support that layout. That would make bitfields much more usable in portable code than they are today. – supercat Apr 26 '18 at 19:28
  • `gcc` and `clang` are permitted to choose a 16-bit ASU for `b`, regardless of its declared type, possibly taking details of other members into consideration to do so. Supposing they do, however, the standard obligates them to put the bits of `c` into the same ASU. Even if they chose an 8-bit ASU for `b`, however, the standard permits them to make the bits of `c` to straddle ASUs. I see nothing non-conforming there. – John Bollinger Apr 26 '18 at 19:45
  • A non-conforming example would be something like this: `struct s { _Bool x : 1; unsigned int y: 1; } example = {1, 1};`, where the bytes of `example` fail to include one having two adjacent bits set. – John Bollinger Apr 26 '18 at 19:48
  • But I completely agree that bitfields in their current form are of pretty limited utility, and that an alternative that permits specifying an exact bitwise layout would be more useful. – John Bollinger Apr 26 '18 at 19:49
  • @JohnBollinger: The compiler isn't choosing a 16-bit ASU for `b`. For purposes of struct layout, it's regarding the whole struct as a 32-bit ASU whose storage is overlapped by non-bitfield members `a` and `e`, but from a behavioral standpoint every non-bitfield members of a struct is required to have storage which doesn't overlap the storage used by any other members, including bitfields. – supercat Apr 26 '18 at 20:14
  • I can only talk about the C semantics and how the observed layout and behavior match up to those. According to C, there are one or more ASUs allocated for storage of bitfield members only, separate from storage for regular structure members. The sizes and alignments of these are unspecified, but some requirements are placed on how bitfields are laid out within. However `gcc` and `clang` actually arrive at the layout that you describe (and that I can replicate here), it can be characterized in the conforming way I already presented. – John Bollinger Apr 26 '18 at 20:49
  • @JohnBollinger: Which would you expect to be larger: `struct q1 { short m1:7; char m2:2; short m3:7; };` or `struct q2 { char m1:7; short m2:2; char m3:7; };` The first ends up being four bytes and the second one two, even though it would seem that the storage unit holding `q1.m1` should have nine bits remaining--plenty to hold `q1.m2`. So why do you suppose it doesn't? – supercat Apr 26 '18 at 21:57
  • I have no basis to form an *expectation* -- implementations have too much freedom. My first guess would be that the two structs are the same size, but C does not require that. It is possible to construct conforming layouts for both cases. In particular, C permits `q1.m7` to be assigned to an 8-bit or 32-bit ASU, datatype not withstanding, as long as the machine can in fact address storage with those granularities. – John Bollinger Apr 26 '18 at 23:51
  • But from your line of discussion, I suspect you may be interpreting some of my earlier comments more broadly than I intended. C does not forbid implementations from considering declared datatypes *at all* when laying out bitfields. Your #2 is non-conforming in the sense that structures exist for which there is no conforming layout consistent with that rule (I gave an example above), but that does not mean that applying the rule produces a non-conforming layout in all cases. – John Bollinger Apr 26 '18 at 23:59
  • @JohnBollinger: The Standard is rather vague as to what kind of addressable storage unit a compiler should be looking for space in. In any case, what really matters for purposes of the original question is what compilers do. BTW, the Standard does not require that implementation decisions have anything whatsoever to do with actual CPU architecture. – supercat Apr 27 '18 at 14:32