1
#include <stdio.h>

int main() {
    typedef struct {
        unsigned int a : 1;
        unsigned char b : 1;
    } bf;

    printf("%ld\n", sizeof(bf));

    return 0;
}

It is a simple code about C struct bit field example, I expected to get 4 but my VisualStudio says it is 8.

Otherwise, according to the compiler website, www.onlinegdb.com, I can get 4 as I expected. so I think my VisualStudio has given me the wrong answer, what am I missing??

+) I have changed the example code, unsigned int a : 3; unsigned char b : 1; to 'unsigned int a : 1; unsigned char b : 1;

because people misundertand that I think sizeof gives 'bits', not 'byte', so I changed it.

I think unsigned int a has 4 bytes already, so unsigned char b can use that already occupied space together.

In the case, I was supposed to get 4 but VisualStudio says 8, that means they use different spaces

chqrlie
  • 131,814
  • 10
  • 121
  • 189
LMSN
  • 19
  • 2
  • 7
    Different compilers might add different padding for structure alignment. And remember that the size reported by `sizeof` is in *bytes* not bits. – Some programmer dude Aug 31 '23 at 07:36
  • 1
    Aside: `printf("%ld\n", sizeof(bf));` ==> `printf ("%zu\n", sizeof (bf));` – Harith Aug 31 '23 at 07:48
  • The smallest addressable unit within a CPU is a byte. not bits. – Raildex Aug 31 '23 at 07:52
  • *I was supposed to get `4`* No, [you're not "supposed" to get any particular answer](https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p11) (bolding mine): "**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. If insufficient space remains, **whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is *implementation-defined***." – Andrew Henle Aug 31 '23 at 10:23
  • (cont) "The **order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is *implementation-defined***. The **alignment of the addressable storage unit is *unspecified***." You **can not** use bit-fields when you need to access specific bits in portable ways. – Andrew Henle Aug 31 '23 at 10:24
  • @AndrewHenle, I would argue that the described behaviour is not caused by any of the rightful implementation choices you've mentioned but, ironically, by the MSVC implementation not packing the bitfields in "adjacent bits of the same unit" – Pignotto Aug 31 '23 at 11:05

4 Answers4

3

In a data model where sizeof(int) == 4, the compiler may either decide to:

  • Store the b member over the 4 bytes occupied by the a member
  • Store the b member immediately after the a member (effectively ignoring the bit-field compaction)
  • Store a and b within a single byte

The case where sizeof(bf) == 4 is probably the first.

The case where sizeof(bf) == 8 is the second. An additional padding of 3 bytes will be needed in order to get the proper alignment when this struct is laid out sequentially in an array. If the size was 5, the second element of an array would be at a non-aligned address. Accessing the int member requires a certain alignment.

Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
1
  1. sizeof gives the size in bytes not bits used in the bitfields.
  2. Compiler is free to padd the struct to meet its alignment requirements. So even if the bitfields only occupy 4 bits and can easily fit in one byte - it can be more.
  3. To remove padding pack the structure. It is compiler specific and GCC uses __attribute__((packed)) https://onlinegdb.com/xleI-KlJZ

otherwise, according to the compiler website, www.onlinegdb.com, I can get '4' as i expected. so i think my VisualStudio has given me the wrong answer, what am i missing??

It is a coincidence only. Simply the size of the struct in bytes is the same as number of your bits. You are not getting the "correct" result.

Change your code to:

int main() {
    typedef struct {
        unsigned int a : 3;
        unsigned char b : 1;
        int: 3;
    }bf;

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

    return 0;
}

and it will still display 4. https://onlinegdb.com/iDdiavGYP

0___________
  • 60,014
  • 4
  • 34
  • 74
0

Your bf struct contains only two bit-fields that sum up to 4 bits, which should fit comfortably within a single byte (8 bits). However the sizeof method returns the size in bytes, not bit. Therefore the result you are getting is actually 8 bytes in Visual Studio and 4 bytes with the online GDB compiler.

Compilers insert padding to align data structures in memory for performance reasons. This is not standardized in C, so different compilers can have different strategies for inserting padding.

Ada
  • 427
  • 2
  • 13
0

There's plenty of uncertainties here, where the compiler may do as it pleases, because bit-fields is a very "underspecified" part of C.

  • unsigned char is not guaranteed to be available for bit-fields - the compiler need not support that type. Only bit-field support for (signed/unsigned) int and _Bool is guaranteed.
  • When dealing with any bit-fields, the compiler is supposed to create an internal chunk of memory referred to as "addressable storage unit". The size and alignment of this "unit" is not defined by the C standard.
  • The bit order within an "addressable storage unit" is not defined by the C standard.
  • The compiler is only required to merge two adjacent bit-fields if there is space remaining in the "addressable storage unit". If there isn't space, then the compiler can allocate the bit-field either across two "addressable storage units" or put it in an "addressable storage unit" of its own. Therefore padding bits may be present.
  • For any struct, bit-field or not, the compiler is free to insert padding bytes between or after any struct members, to correct alignment.
  • Bit-fields and structs may behave differently depending on endianess, in case they contain endianess-dependent members.

Therefore in a system where sizeof(int) == 4, the compiler may do any of the following with your code:

  • Refuse to compile the code if unsigned char bit-fields are not supported.
  • Merge the two struct members into the same memory chunk, in an unspecified way, where you cannot assume the bit-order.
  • Place the two struct members in different memory chunks and you cannot assume the bit-order in either of them.

In any of these scenarios it may also add as many padding bytes as it likes: between the struct members of after them. Apparently VS decided to allocate each member in a chunk of 32 bits of its own and then pad accordingly. That's probably a rather common way to implement it.

The best solution to all of these problems is not never use bit-fields for any purpose. As you detected, they are pretty pointless to use since they don't guarantee that any memory gets saved - rather the contrary.

Instead use bit shifts and bit masking on unsigned integer types of suitable size - that's fully portable, fully deterministic and as a bonus does not depend on CPU endianess either.

Lundin
  • 195,001
  • 40
  • 254
  • 396