#pragma包的奇怪行为

时间:2019-07-11 13:48:23

标签: c++ gcc struct pragma gcc4.4

使用#pragma pack时遇到一个奇怪的问题。

我具有以下结构:

  1. st_a:与#pragma pack(1)一起打包。大小= 1个字节。包含位域。
  2. st_b:与#pragma pack(2)一起打包。大小= 14字节。
  3. st_c:与#pragma pack(2)一起打包。大小= 16字节。包含st_ast_b
  4. st_d:与#pragma pack(2)一起打包。大小= 16字节。包含st_ast_bst_b的成员)的内容

因此,由于st_a#pragma pack(1)下是1个字节,并且由于它在st_c下是在#pragma pack(2)里面,所以应该有一个额外的填充字节st_c之后的st_a中,该多余的字节后面应跟st_b的内容,后者是长度为偶数(10)的字符缓冲区。

但是,当我拿出st_b的内容并将其直接放入st_a时,这件事就奇怪了。在字符缓冲区之后而不是之前看到填充(请参见下面的输出)。

有人可以解释为什么会发生这种奇怪的行为吗?

代码:

#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;
}

输出:

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

注意:我使用的是GCC 4.4.x版。

关于输出的一些注释

在第一行中,5b是在4(即1个字节st_a)和41(是{{1}的缓冲区的第一个字符)之间引入的填充字节}。

在第二行中,st_b是在缓冲区的最后一个字符04a中的字符缓冲区后面的整数a之间引入的填充字节。

第三行打印所有结构的大小。

1 个答案:

答案 0 :(得分:0)

您没有正确检测填充。您是否期望填充为零字节?没有理由期望这一点。填充字节中的值可以是任何值。 您在结尾处看到的零是st_b.x1的4个字节的一部分。它的值是10,它是一个小的endian 2的补码整数,存储为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

查看填充的更好方法是使用offsetof宏。例如:

#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;
}

这将输出:

offset of p1: 0, offset of p2: 2

清楚地表明p1p2之间只有一个填充字节。

将填充视为零的另一种方法是在声明结构后立即将结构的所有字节清零:

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

然后您的程序输出将如下所示:

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