0

I have the following code:

#include <iostream>
#include <bitset>

#pragma pack(1)
typedef uint8_t flag_t;
typedef struct flag_struct_t {
        flag_t f1:1;
        flag_t f2:2;
        flag_t f3:2;
        flag_t f4:2;
        flag_t f5:1;
} flag_struct_t;


int main() {
    const uint8_t flagValue = 96;
    std::bitset<8> mybits(flagValue);
    const flag_struct_t flag = *reinterpret_cast<const flag_struct_t*>(&flagValue);
    std::cout << "f2 = " << (uint16_t)flag.f2 << std::endl;
    std::cout << "f3 = " << (uint16_t)flag.f3 << std::endl;
    std::cout << "f4 = " << (uint16_t)flag.f4 << std::endl;
    std::cout << "bitset = " << mybits << std::endl;
    std::cout << "size of flag_struct_t = " << sizeof(flag_struct_t) << std::endl;
}

#pragma pack()

The output is:

$ ./mybitset 
f2 = 0
f3 = 0
f4 = 3
bitset = 01100000
size of flag_struct_t = 1

It seems like the order of struct members has been reversed from f1, f2, f3, f4 to f4, f3, f2, f1.

Why is that?

I am using GCC 8 if it matters.

Thanks!

HCSF
  • 2,387
  • 1
  • 14
  • 40
  • 4
    Possible duplicate of [Why and how are C++ bitfields non-portable?](https://stackoverflow.com/q/38197460/608639) and *"Bit fields are non-portable in the sense that the ordering of the bit is unspecified. So the bit at index 0 with one compiler could very well be the last bit with another compiler"*. – jww Mar 28 '19 at 09:03

2 Answers2

3

First of all, your program has undefined behavior due to the type punning through reinterpret_cast. Second, the layout of bitfields is implementation-defined ([class.bit]/1), so there is no guarantee how the members of a bitfield will be allocated to begin with. But let's assume for now that the compiler will be so nice and actually turn this into code that does what you expect this to do.

The binary representation of decimal 96 is 01100000. Note that numbers are generally written from right to left (presumably, due to their Arabic origins). For example, the "first digit" (least-significant digit) in the decimal number 123 would be 3, not 1. Binary is no different. So if we assume the compiler packs the members of your bitfield in the order they are declared, starting at the first bit, then the layout should look like this

Bit  7  6  5  4  3  2  1  0  
    f5 f4 f4 f3 f3 f2 f2 f1

or, for the specific values used in your example

Bit  7  6  5  4  3  2  1  0  
     0  1  1  0  0  0  0  0

which is exactly what you're seeing, if I'm not mistaken…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • yeah, I noticed the first issue. Tho, GCC doesn't throw warnings (while C style casting would trigger warnings). And I think most of the network payload related code is casting this way. What's the best practice other than `memcpy()`? For the layout of bitfields, according to your link, it seems like machine specific -- `Bit-fields are assigned right-to-left on some machines, left-to-right on others`? Tho, the [link](https://gcc.gnu.org/ml/gcc/2004-09/msg00581.html) from @eerorika suggested that the order of bitfields is kind of OS dependent as it was talking about Redhat's patch. – HCSF Mar 28 '19 at 03:27
  • In general, `memcpy()` is currently the only way to do it. And even that works only in a very few cases. There is no well-defined way to access the contents of a bunch of bitfields as an integer as far as I'm aware… – Michael Kenzel Mar 28 '19 at 04:16
  • Concerning the layout: it's implementation-defined, i.e., depends on the compiler. And what the compiler will do will generally depend on the target platform/machine, compiler flags, … – Michael Kenzel Mar 28 '19 at 04:19
  • @HCSF if you want reliable layout of bits, use [fixed-width integer types](https://en.cppreference.com/w/cpp/types/integer) and manually access the bits via bitwise operations… – Michael Kenzel Mar 28 '19 at 04:26
  • Thanks Michael. You said, "even that works only in a very few cases". I thought `memcpy()` always works (although it is not optimal in a sense that it requires one extra copy and slower). No? – HCSF Mar 28 '19 at 05:55
2
const flag_struct_t flag = *reinterpret_cast<const flag_struct_t*>(&flagValue);

This reinterpretation has undefined behaviour.

It seems like the order of struct members has been reversed from f1, f2, f3, f4 to f4, f3, f2, f1.

Why did you expect the order to be one rather than the other? The order of bit field members is implementation defined.

GCC puts bit fields starting from "first bit" i.e. lowest order bit in little endian and highest order bit in big endian systems: https://gcc.gnu.org/ml/gcc/2004-09/msg00581.html

eerorika
  • 232,697
  • 12
  • 197
  • 326