2

Consider this code:

#include <stdio.h>
#include <stdint.h>

#ifdef __GNUC__
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif

#ifdef _MSC_VER
#define PACK( __Declaration__ ) __pragma(pack(push, 1)) __Declaration__ __pragma(pack(pop))
#endif

PACK(struct S
{
  uint8_t  f0;
  uint32_t f1;
  uint32_t f2 : 17;
});

int main()
{
    printf("%zu\n", sizeof(struct S));
    return 0;
}

No compiler options were given.

Output:

gcc   (9.2.0): 8
clang (8.0.1): 8
cl    (19.23.28106.4 for x86): 9

Why in case of cl result of sizeof is 9?

What the standard says?

pmor
  • 5,392
  • 4
  • 17
  • 36
  • IMHO the only correct one is the output from `cl`. Maybe the other two have too zelant optimizers... Try to assign values to structure and use them in a function to force compiler to implement the right space. – Frankie_C Mar 20 '20 at 10:49
  • People tend to make a lot more assumptions about bitfield semantics and representation than are supported by the C language specification. Throw in extensions such as structure packing, too, and you'd better hold on to your hat. – John Bollinger Mar 20 '20 at 11:52
  • @Frankie_C Clang and GCC bit-pack i.e. they only store bytes that are used. MSVC probably only byte-packs structures. – S.S. Anne Mar 20 '20 at 11:53
  • 1
    @S.S.Anne I had a look around, it seems that the different interpretation of `storage unit` is: for GCC/CLANG it is the basic memory unit (in this case an 8bits byte), for MS it is the last type used for the bitfield specifier. So both comply with C standard. – Frankie_C Mar 20 '20 at 16:36

2 Answers2

2

The C standard C17 6.7.2.1/11 says, emphasis mine:

An implementation may allocate any addressable storage unit large enough to hold a bitfield. 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. 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.

These "storage units" are only known internally by the compiler and can have different size on different compilers. The concept of bit/byte "packing" is not supported by the standard and also behaves differently from compiler to compiler.

None of the compilers used in your test violates the C standard, since this is 100% implementation-defined behavior.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

The requests for packing request that the compiler pack the members into the structures, with no padding. However, they do not change the types or representations of the members themselves. It appears GCC and Clang use three bytes to represent a bit-field of 17 bits and Microsoft uses four, at least when the base type is uint32_t. Thus, for this structure, Clang and GCC pack a one-byte object, a four-byte object, and a three-byte object into eight bytes, while Microsoft packs a one-byte object, a four-byte object, and a four-byte object into nine bytes.

This may be related to the fact that Microsoft‘s compiler is primarily a C++ compiler and C and C++ treat the types of bit-fields differently. In C, the type of a bit-field may be implementation-defined (the standard is not entirely clear). In C++, the type of a bit-field is its base type.

We can test this by considering a rearrange unpacked structure:

struct S
{
    uint8_t  f0;
    uint32_t f2 : 17;
    uint32_t f1;
};

GCC and Clang show sizeof(struct S) to be eight bytes, which is consistent with a three-byte representation for the bit-field. MSVC shows twelve bytes, which is consistent with a four-byte representation for the bit-field.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312