扩展填充结构时,为什么不能在尾部填充中放置额外的字段?

dea*_*nix 37 c c++ struct memory-alignment

让我们考虑结构:

struct S1 {
    int a;
    char b;
};

struct S2 {
    struct S1 s;       /* struct needed to make this compile as C without typedef */
    char c;
};

// For the C++ fans
struct S3 : S1 {
    char c;
};
Run Code Online (Sandbox Code Playgroud)

S1的大小为8,由于对齐而预期.但是S2和S3的大小是12.这意味着编译器将它们构造为:

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
|       a       | b |  padding  | c |  padding  |
Run Code Online (Sandbox Code Playgroud)

编译器可以在不破坏对齐约束的情况下将c放在6 7 8中的填充中.什么是阻止它的规则,它背后的原因是什么?

Ker*_* SB 21

简短回答(对于问题的C++部分):由于历史原因,Itanium ABI for C++禁止使用POD类型的基础子对象的尾部填充.请注意,C++ 11没有这样的禁令.允许通过其基础表示复制简单可复制类型的相关规则3.9/2明确排除了基础子对象.


答案长:我会立刻尝试对待C++ 11和C.

  1. S1必须包括填充的布局,因为S1::a必须对齐int,并且数组S1[N]由连续分配的类型对象组成S1,每个对象a必须如此对齐.
  2. 在C++中,T非基本子对象的平凡可复制类型的对象可以被视为sizeof(T)字节数组(即,您可以将对象指针强制转换为对象unsigned char *,并将结果视为指向a的第一个元素的指针unsigned char[sizeof(T)],并且值为此数组确定对象).由于C中的所有对象都属于这种类型,因此这解释S2了C和C++.
  3. C++剩下的有趣案例是:
    1. 基础子对象,不受上述规则约束(参见C++ 11 3.9/2),和
    2. 任何不是简单可复制类型的对象.

对于3.1,确实存在常见的,流行的"基本布局优化",其中编译器将类的数据成员"压缩"到基础子对象中.当基类为空(∞%大小减小!)时,这是最引人注目的,但更普遍适用.但是,我在上面链接的并且许多编译器实现的Itanium ABI for C++在相应的基类型为POD时禁止这种尾部填充压缩(并且POD意味着可以简单地复制和标准布局).

对于3.2,Itanium ABI的相同部分适用,但我目前不认为C++ 11标准实际上要求任意的,非平凡可复制的成员对象必须具有与相同类型的完整对象相同的大小.


以前的答案一直供参考.

我相信这是因为S1标准布局,因此出于某种原因,未受影响的 - S1对象S3仍然存在.我不确定这是否符合标准.

但是,如果我们转向S1非标准布局,我们会观察布局优化:

struct EB { };

struct S1 : EB {   // not standard-layout
    EB eb;
    int a;
    char b;
};

struct S3 : S1 {
    char c;
};
Run Code Online (Sandbox Code Playgroud)

现在sizeof(S1) == sizeof(S3) == 12在我的平台上.现场演示.

这是一个更简单的例子:

struct S1 {
private:
    int a;
public:
    char b;
};

struct S3 : S1 {
    char c;
};
Run Code Online (Sandbox Code Playgroud)

混合访问使S1非标准布局.(现在sizeof(S1) == sizeof(S3) == 8.)

更新:的决定性因素似乎是琐碎以及标准layoutness,即类必须是POD.以下非POD标准布局类是基本布局可优化的:

struct S1 {
    ~S1(){}
    int a;
    char b;
};

struct S3 : S1 {
    char c;
};
Run Code Online (Sandbox Code Playgroud)

再次sizeof(S1) == sizeof(S3) == 8.演示


Bil*_*nch 18

我们来考虑一些代码:

struct S1 {
    int a;
    char b;
};

struct S2 {
    S1 s;
    char c;
};
Run Code Online (Sandbox Code Playgroud)

让我们考虑如果sizeof(S1) == 8和将会发生什么sizeof(S2) == 8.

struct S2 s2;
struct S1 *s1 = &(s2.s);
memset(s1, 0, sizeof(*s1));
Run Code Online (Sandbox Code Playgroud)

你现在已经被覆盖了S2::c.


出于阵列对齐的原因,S2也不能具有9,10或11的大小.因此下一个有效大小为12.

  • @KerrekSB这个答案中的S1和S2示例不依赖继承. (3认同)