Search code examples
visual-c++structalignmentpragma-pack

How should #pragma pack(8) work?


I'm new to structure alignment and packing. I thought I understood it, but I'm finding some results that I did not expect (see below).

My understanding of structure alignment is:

  1. Types are generally aligned on memory addresses that are multiples of their size.

  2. Padding is added as needed to facilitate proper alignment

  3. The end of a structure must be padded out to a multiple of the largest element (to facilitate array access)

The #pragma pack directive basically allows overriding the general convention of aligning based on a type's size:

#pragma pack(push, 8)
struct SPack8
{
  // Assume short int is 2 bytes, double is 8 bytes, and int is 4 bytes
  short int a;
  double b;
  short int c;
  int d;
};
#pragma pack(pop)

Pseudo struct layout: What I expected:
// note: PADDING IS BRACKETED
0, 1, [2, 3, 4, 5, 6, 7] // a occupies address 0, 1
8, 9, 10, 11, 12, 13, 14, 15, // b occupies 8-15 inclusive
16, 17, [18, 19, 20, 21, 22, 23] // c occupies 16-17 inclusive
24, 25, 26, 27 // d occupies 24-27 inclusive
// Thus far, SPack8 is 28 bytes, but the structure must be a multiple of
// sizeof(double) so we need to add padding to make it 32 bytes
[28, 29, 30, 31]

To my surprise, sizeof(SPack8) == 24 on VS 2015 x86. It seems that d is not being aligned on an 8 byte address:

offsetof(SPack, a) // 0, as expected
offsetof(SPack, b) // 8, as expected
offsetof(Spack, c) // 16, as expected
offsetof(SPack, d) // 20..what??

Can someone please explain what is happening/what I have misunderstood?

Thank you!


Solution

  • Your misunderstanding is that #pragma pack lets you widen the struct, it doesn't. pack allows you to pack the struct more tightly if needed. The #pragma pack(push, 8) tells the compiler, that it can at most align on 8-byte boundary, but not more.

    Example:

    #pragma pack(push, 2)
    struct X {
        char a; // 1 byte
        // 1 byte padding
        int b; // 4 bytes, note though that it's aligned on 2 bytes, not 4.
        char c, d, e; // 3 bytes
        //1 byte padding
    }; // == 10 bytes, the whole struct is also aligned on 2 bytes, not 4
    #pragma pack(pop)
    
    // The same struct without the pragma pack:
    struct Y {
        char a; // 1 byte
        // 3 bytes padding
        int b; // 4 bytes
        char c, d, e; // 3 bytes
        // 1 byte padding
    };
    

    This is what pack does, using less padding as the compiler would usually use. In your example you tried to align the int on 8-byte boundary but since you allowed the compiler to align on at most 8 bytes, the 4 byte alignment which the compiler would like to use is fine. Your whole struct which is of size 24 also has a size which is a multiple of 8 (your largest member), so no padding needed to fill it up to 32.

    You can forcefully align your struct

    __declspec(align(32)) struct Z {
        char a;
        int b;
        char c, d, e;
    };
    

    or even a member of your struct

    struct SPack8
    {
      // Assume short int is 2 bytes, double is 8 bytes, and int is 4 bytes
      short int a;
      double b;
      short int c;
      __declspec(align(8)) int d;
    };
    

    on a particular boundary, but I don't see a reason to force a 4-byte type to be aligned on 8 bytes.