【问题标题】:Strange behavior of #pragma pack#pragma pack 的奇怪行为
【发布时间】:2019-07-11 13:48:23
【问题描述】:

我在使用#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_b 的内容(st_b 的成员)

因此,由于st_a 是在#pragma pack(1) 下打包的 1 个字节,并且由于它在st_c 内部,而#pragma pack(2) 打包在st_c 中,紧跟在@987654340 之后应该有一个额外的填充字节@ 并且那个额外的字节后面应该跟 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 之间引入的填充字节,这是一个字节st_a41,它是st_b 的缓冲区的第一个字符。

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

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

【问题讨论】:

  • 在询问编译器扩展时,您应该告诉我们您使用的编译器。
  • @eerorika,我已经更新了我的问题!
  • @RaymondChen,我同意你的看法!你能否澄清我的另一个疑问,如果st_c 中的缓冲区大小是奇数长度怎么办?
  • st_dst_a 之后不需要填充,因为下一个字段是可以字节对齐的unsigned char。在st_a 之后的st_c 中需要填充,因为下一个字段是st_b,它被标记为需要2 字节对齐。想象一下,如果有一个新结构st_e,它与st_c 相同,但具有 4 字节对齐。你不能指望st_d 猜测buf1x1 是内联st_c 还是内联st_e
  • 那么st_b 将是一个(比如说)9 字节的buf,一个填充字节,然后是一个int

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


【解决方案1】:

您没有正确检测到填充。您是否期望填充为零字节?没有理由期待这一点。填充字节中的值可以是任何值。 最后看到的零是st_b.x1 的4 个字节的一部分。它的值是 10,它作为小端 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

【讨论】:

  • 我知道5b 是填充字节。问题有所不同,请仔细阅读问题。
  • 感谢您的努力 :-)。我确实学到了一些新东西:offsetof!
  • 是的,我现在明白了。如果您将问题简化为最基本的问题,那就更清楚了。您的示例中有很多不相关的额外代码,例如st_a 中的位字段和st_b 中的int。我认为 Raymond Chen 的 cmets 回答了你的问题。
猜你喜欢
  • 1970-01-01
  • 2014-05-10
  • 1970-01-01
  • 2016-01-15
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 2016-01-30
  • 2013-09-03
相关资源
最近更新 更多