1

I'm trying to pack a struct in C++ like this:

#pragma pack(push, 1)

typedef struct // struct should be 4 byte in size
  {
    BYTE mValue1;
    BYTE mValue2;
    UINT mValue3 : 5;
    UINT mValue4 : 11;
  } MyPackedStruct;

  // Size should be 4, but it actually is 6
const int kMyPackedStructSize = sizeof(MyPackedStruct);

#pragma pack( pop )

I would have expected kMyPackedStructSize to be 4 bytes, but it is not. The compiler tells me it is 6. Is there something wrong with my packing code? Or did I misunderstand the whole concept?

Boris
  • 8,551
  • 25
  • 67
  • 120

3 Answers3

2

Most platforms today have int (UINT) as 4 bytes. That would give the 6 bytes.

mksteve
  • 12,614
  • 3
  • 28
  • 50
  • Ah, OK. Changed it tounsigned int mType : 8; unsigned int mChannel : 8; unsigned int mReserved : 5; unsigned int mDataLength : 11;, now it works. Thanks! – Boris Sep 02 '15 at 06:40
2

If you really want to know what's happening, you need to look at the level below C. For example, the following program will tell you how big the total structure is, as well as the BYTE fields:

#include <stdio.h>

typedef unsigned char BYTE;
typedef unsigned int UINT;

#pragma pack(push, 1)
typedef struct {
    BYTE mValue1;    BYTE mValue2;
    UINT mValue3:5;  UINT mValue4:11;
} MyPackedStruct;
MyPackedStruct x;
#pragma pack( pop )

int main (void) {
    printf ("%zd\n", sizeof(x));
    printf ("%zd\n", sizeof(x.mValue1));
    return 0;
}

Unfortunately, you can't get the size of a bit field in the same way so, to get that, you'd need to go down to the assembly level, with something like:

#include <stdio.h>

typedef unsigned char BYTE;
typedef unsigned int UINT;

#pragma pack(push, 1)
typedef struct {
    BYTE mValue1;    BYTE mValue2;
    UINT mValue3:5;  UINT mValue4:11;
} MyPackedStruct;
MyPackedStruct x;
#pragma pack( pop )

int main (void) {
    x.mValue3 = 21;    //       10101
    x.mValue4 = 1198;  // 10010101110
    return 0;
}

The basic idea would be to turn that into assembly and then examine the code to see how it sets the values. That will let you know how they're structured under the covers. For example, under my gcc environment, it loads the individual bits into eax but only uses the lower 8 bits in al, and it comes out:

movzbl  _x+2, %eax
andl    $-32, %eax    ;; -32 = 111_00000, clears lower 5 bits
orl     $21, %eax     ;; then store 21 (10101) into those bits
movb    %al, _x+2     ;; hence val3 uses lower 5 bits of third byte

movzbl  _x+2, %eax
andl    $31, %eax     ;; 31 = 000_11111, clear all upper bits
orl     $-64, %eax    ;; -64 = 110_00000, 110 is the (lower of 1198)
movb    %al, _x+2

movzbl  _x+3, %eax
andl    $0, %eax      ;; clear all bits
orl     $-107, %eax   ;; stores 10010101 into fourth byte (upper of 1198)
movb    %al, _x+3     ;; so val4 uses all fourth byte and 3 bits of third

So, in this particular case, the full size is 32 bits, allocated thus:

--- increasing memory addresses -->
11111111 22222222 55533333 44444444
\______/ \______/ \_/\___/ \______/
 value1   value2   | value3   | value4
                   \__________/  (4s then 5s)

Now you may end up with a totally different layout, this sort of thing is left up to the implementation after all. I'm just showing you a couple of ways to figure it out.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
1
  • A char (one byte) will be 1-byte aligned.
  • A short (two bytes) will be 2-byte aligned.
  • An int (four bytes) will be 4-byte aligned.
  • A long (four bytes) will be 4-byte aligned.
  • A float (four bytes) will be 4-byte aligned.
  • A double (eight bytes) will be 8-byte aligned on Windows and 4-byte aligned on Linux (8-byte with -malign-double compile time option).
  • A long long (eight bytes) will be 8-byte aligned.
  • A long double (ten bytes with C++Builder and DMC, eight bytes with Visual C++, twelve bytes with GCC) will be 8-byte aligned with C++Builder, 2-byte aligned with DMC, 8-byte aligned with Visual C++ and 4-byte aligned with GCC.
  • Any pointer (four bytes) will be 4-byte aligned. (e.g.: char*, int*)

The only notable differences in alignment for an LP64 64-bit system when compared to a 32-bit system are:

  • A long (eight bytes) will be 8-byte aligned.
  • A double (eight bytes) will be 8-byte aligned.
  • A long double (eight bytes with Visual C++, sixteen bytes with GCC) will be 8-byte aligned with Visual C++ and 16-byte aligned with GCC.
  • Any pointer (eight bytes) will be 8-byte aligned.

Some data types are dependent on the implementation.

#pragma pack(push, 1)

    typedef struct // struct should be 4 byte in size
      {
        unsigned char mValue1;
        unsigned char mValue2;
        unsigned char mValue3 : 5;
        unsigned char mValue4 : 11;
      } MyPackedStruct;

      // Size should be 4 now, but depends if unsigned char will work for you
    const int kMyPackedStructSize = sizeof(MyPackedStruct);

Hope it shelps you understand, even if i declared your struct wrong.