Strange behavior of #pragma pack

406 Views Asked by At

I am facing a strange issue when using #pragma pack.

I have the following structs:

  1. st_a: packed with #pragma pack(1). Size = 1 byte. Contains bitfields.
  2. st_b: packed with #pragma pack(2). Size = 14 bytes.
  3. st_c: packed with #pragma pack(2). Size = 16 bytes. Contains st_a and st_b
  4. st_d: packed with #pragma pack(2). Size = 16 bytes. Contains st_a and contents of st_b (st_b's members)

So, since st_a is of 1 byte packed under #pragma pack(1), and since it's inside st_c which is packed under #pragma pack(2), there should be one extra byte of padding in st_c immediately after st_a and that extra byte should be followed by contents of st_b which is a character buffer of even length (10).

But, this thing works strangely when I take the contents of st_b out and put them directly in st_a. The padding is seen after the character buffer, rather than before (See output below).

Can somebody explain, why this strange behavior occurs?

Code:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

#pragma pack(push)
#pragma pack(1)
typedef struct A {
    int a : 1;
    int b : 1;
    int c : 1;
    int d : 1;
    int e : 1;
    int f : 1;
    int g : 1;
    int h : 1;
} st_a;
#pragma pack(pop)

#pragma pack(push)
#pragma pack(2)
typedef struct B {
    unsigned char buf[10];
    int x;
} st_b;

typedef struct C {
    st_a temp1;
    st_b temp2;
} st_c;

typedef struct D {
    st_a temp3;
    unsigned char buf1[10];
    int x1;
} st_d;
#pragma pack(pop)

void print_hex(unsigned char* packet) {
    for (int i = 0; i < 16; i++) {
        printf("%x ", packet[i]);
    } printf("\n");
}

int main() {
    st_c one;
    one.temp1.a = 0;
    one.temp1.b = 0;
    one.temp1.c = 1;
    one.temp1.d = 0;
    one.temp1.e = 0;
    one.temp1.f = 0;
    one.temp1.g = 0;
    one.temp1.h = 0;
    memcpy(&one.temp2.buf, "ABCDEFGHIJ", 10);
    one.temp2.x = 10;

    st_d two;
    two.temp3.a = 0;
    two.temp3.b = 0;
    two.temp3.c = 1;
    two.temp3.d = 0;
    two.temp3.e = 0;
    two.temp3.f = 0;
    two.temp3.g = 0;
    two.temp3.h = 0;
    memcpy(&two.buf1, "ABCDEFGHIJ", 10);
    two.x1 = 10;

    print_hex((unsigned char*) &one);
    print_hex((unsigned char*) &two);
    cout << sizeof(st_c) << " " << sizeof(st_a) << " " << sizeof(st_b) << " " << sizeof(st_d) << endl;

    return 0;
}

Output:

4 5b 41 42 43 44 45 46 47 48 49 4a a 0 0 0
4 41 42 43 44 45 46 47 48 49 4a 0 a 0 0 0
16 1 14 16

Note: I am using GCC version 4.4.x.

Some notes about the output:

In first line, 5b is the padding byte introduced between 4 which is 1 byte st_a and 41 which is the first character of buffer of st_b.

In second line, 0 is the padding byte introduced between 4a which is last character of buffer and a which is the integer following the character buffer in st_d.

Third line prints the size of all the structures.

1

There are 1 best solutions below

3
On

You are not detecting the padding correctly. Are you expecting the padding to be zero bytes? There is no reason to expect that. The value in the padding bytes can be anything. The zeroes you are seeing at the end are part of the 4 bytes of st_b.x1. It's value is 10 which as a little endian 2's complement integer with be stored as 0A 00 00 00

04 5b 41 42 43 44 45 46 47 48 49 4a 0a 00 00 00
^  ^  ^-----------------------------^---------^
|  |  st_b.buf                      st_b.x1  
|  random padding byte
|
st_a

A better way to look at the padding is to use the offsetof macro. For example:

#include <stdio.h>
#include <stddef.h>

#pragma pack(push)
#pragma pack(1)
struct Pack1 {
  char x;
};

#pragma pack(2)
struct Pack2 {
  short y;
};

struct Combined {
  struct Pack1 p1;
  struct Pack2 p2;
};
#pragma(pop)

int main()
{
  printf("offset of p1: %u, offset of p2: %u\n",
         offsetof(struct Combined, p1),
         offsetof(struct Combined, p2));
 return 0;
}

This outputs:

offset of p1: 0, offset of p2: 2

which clearly shows there is a single padding byte between p1 and p2.

Another way to see the padding as zeroes would be to zeroize all the bytes of the struct right after it's declaration:

st_c one;
memcpy(&one, 0, sizeof(st_c);

Then the output of you program would look like:

04 00 41 42 43 44 45 46 47 48 49 4a 0a 00 00 00