Bit field structures in C++ I just recently learned how to use Bit Field Structures in C++, and I was thinking of applying it to a project. It would certainly make the project code much easier to work with. So, I typed up a quick test program to see if it would work as I expected, and it does. But, I'm not 100% convinced that it is safe. 

I am fairly confident my CRC16 union is Endianess specific, but I'm not sure about the Bit Field struct, I suspect it is... Can anyone confirm this?


Is this usage of a bit field struct safe?
Can it be made safer?
Is it Endianess specific?


Note: So far, I have only tested this on 64 bit Linux.



#include &lt;stdint.h&gt;
#include &lt;cstring&gt;
#include &lt;iostream&gt;
#include &lt;iomanip&gt;

typedef union 
{
    int16_t Value;
    struct
    {
        int8_t Lo;
        int8_t Hi;
    } Bytes; //This is all I could think of to call this. If you can think of something better, let me know.
} CRC16;

#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
typedef struct
{
    uint8_t SOH;
    uint16_t Count : 14;
    uint16_t Select : 1;
    uint16_t Qsync : 1;
    uint8_t Resp;
    uint8_t Num;
    uint8_t Addr;
    CRC16 Blck;
#ifdef _MSV_VER
} DataHeader;
#pragma pack(pop)
#else
}  __attribute__((packed)) DataHeader;
#endif

template&lt;typename T&gt; 
void PrintHex(std::string Description, T Value, long Mask = -1)
{
    using namespace std;
    cout &lt;&lt; hex
         &lt;&lt; setiosflags(ios_base::showbase)
         &lt;&lt; Description 
         &lt;&lt; " = "
         &lt;&lt; (static_cast&lt;unsigned short&gt;(Value) &amp; Mask)
         &lt;&lt; resetiosflags(ios_base::showbase)
         &lt;&lt; endl;
}

int main()
{

    DataHeader test;
    int8_t packet[8] = {0x64,0x42,0x81,0x42,0x43,0x01,0x32,0x97};

    //this is the most important part, as this structure would be 
    //passed to Windows API calls, potentially file and socket I/O
    //functions, etc.
    memcpy(&amp;test, packet, sizeof(packet));

    std::cout &lt;&lt; "sizeof(DataHeader) = " &lt;&lt; sizeof(DataHeader) &lt;&lt; std::endl;
    PrintHex("SOH", test.SOH);
    PrintHex("Count", test.Count);
    PrintHex("Select", test.Select);
    PrintHex("Qsync", test.Qsync);
    PrintHex("Resp", test.Resp);
    PrintHex("Num", test.Num);
    PrintHex("Addr", test.Addr);
    PrintHex("Blck.Value", test.Blck.Value);
    PrintHex("Blck.Bytes.Lo", test.Blck.Bytes.Lo, 0xFF);
    PrintHex("Blck.Bytes.Hi", test.Blck.Bytes.Hi, 0xFF);

    return 0;

}




Output


  sizeof(DataHeader) = 8
  SOH = 0x64
  Count = 0x142
  Select = 0
  Qsync = 0x1
  Resp = 0x42
  Num = 0x43
  Addr = 0x1
  Blck.Value = 0x9732
  Blck.Bytes.Lo = 0x32
  Blck.Bytes.Hi = 0x97
#include &lt;stdint.h&gt;


At least officially, you're supposed to prefer to use &lt;cstdint&gt; instead. I have a hard time getting very excited about it though.

#include &lt;cstring&gt;


From the looks of the code, you really wanted to include &lt;string&gt; here instead. You use an std::string in one place anyway (and I don't any use of anything from &lt;cstring&gt;).

typedef union 
{
    int16_t Value;
    struct
    {
        int8_t Lo;
        int8_t Hi;
    } Bytes; //This is all I could think of to call this. If you can think of something better, let me know.
} CRC16;


This usage is fairly typical in C, but in C++, I'd just use:

union CRC16 { 
    // ...
};


Just like with class/struct, there's no need for a typedef in C++.

As you guessed, this is endian-specific. Given that this is apparently an old DEC protocol, chances are that it's also little-endian. That's good to the degree that it works fine with Intel processors, but bad to the degree that the normal ntohl, ntohs (etc.) are all defined in terms of big-endian network format, so you can't use them to easily write portable code that converts endianness when needed.

#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
typedef struct


Same comment as above--no real need for a typedef in C++.

uint16_t Count : 14;
uint16_t Select : 1;
uint16_t Qsync : 1;


You probably want to be aware that the mapping from bitfields to bits can vary (even independent of endianness). Not necessarily something you want to change or can fix, but with a different compiler (even with the same CPU) this could rather suddenly break.

#ifdef _MSV_VER


Minor typo--this should almost certainly be _MSC_VER.

template&lt;typename T&gt; 
void PrintHex(std::string Description, T Value, long Mask = -1)
{
    using namespace std;
    cout &lt;&lt; hex
         &lt;&lt; setiosflags(ios_base::showbase)
         &lt;&lt; Description 
         &lt;&lt; " = "
         &lt;&lt; (static_cast&lt;unsigned short&gt;(Value) &amp; Mask)
         &lt;&lt; resetiosflags(ios_base::showbase)
         &lt;&lt; endl;
}


This has (IMO) a problem: after it's done, the hex flag is reset, even if it may have been set before the function was called. I'd rather use ios_base::flags() to retrieve the current flags, when again to restore then when finished. As (nearly) always when taking some action like that which you want to do, then un-do later, I'd prefer to put it into a small class that uses RAII to automate the restoring:

class flag_rest {
    std::ios_base::fmtflags original;
    std::ostream &amp;s;
public:
    flag_rest(std::ostream &amp;s) : original(s.flags()), s(s) {}
    ~flag_rest() { s.flags(original); }
};


Using that, printhex gets a little simpler:

template&lt;typename T&gt;
void PrintHex(std::string Description, T Value, long Mask = -1)
{
    using namespace std;
    flag_rest f(cout);

    cout &lt;&lt; showbase
        &lt;&lt; hex
        &lt;&lt; Description
        &lt;&lt; " = "
        &lt;&lt; (static_cast&lt;unsigned short&gt;(Value) &amp; Mask)
        &lt;&lt; endl;
}


Probably more importantly, it also restores the flag to the state it held before the call, and does so dependably (even if the function exits via an exception). Of course, it's also nice that flag_rest is fairly reusable as well.

Of course, the cast to unsigned short means this can (will) misbehave or any type larger than a short, though I doubt you really care a lot about it.