为什么这个结构体填充技巧有效?

Why does this struct padding trick work?

本文关键字:有效 填充 结构体 为什么      更新时间:2023-10-16

考虑这个简单的程序

#include <iostream>
struct A
{
    int   x1234;
    short x56;
    char  x7;
};
struct B : A
{
    char x8;
};
int main()
{
    std::cout << sizeof(A) << ' ' << sizeof(B) << 'n';
    return 0;
}

打印8 12。尽管B可以在不破坏对齐要求的情况下打包成8字节,但它却占用了贪婪的12字节。

sizeof(B) == 8会很好,但答案是结构体的大小是否需要是该结构体对齐的精确倍数?说明没有办法。

因此,当下面的

struct MakePackable
{
};
struct A : MakePackable
{
    int   x1234;
    short x56;
    char  x7;
};
struct B : A
{
    char x8;
};

打印8 8

这是怎么回事?我怀疑标准布局类型与此有关。如果是这样,那么当该特性的唯一目的是确保与C的二进制兼容性时,它导致上述行为的理由是什么?


编辑:正如其他人指出的那样,这是ABI或特定于编译器的,所以我应该补充说,使用以下编译器在x86_64-unknown-linux-gnu上观察到这种行为:
  • 叮当声3.6
  • gcc 5.1

我还注意到clang的struct dump中有一些奇怪的东西。如果我们要求没有尾部填充("dsize")的数据大小

          A   B
first     8   9
second    7   8

,那么在第一个例子中我们得到dsize(A) == 8。为什么不是7?

这是一个数据点,但不是一个完整的答案。

假设我们有(作为一个完整的翻译单元,而不是一个代码片段):

struct X {};
struct A
{
    int   x1234;
    short x56;
    char  x7;
}
void func(A &dst, A const &src) 
{
    dst = src;
}

在g++中,这个函数被编译为:

movq    (%rdx), %rax
movq    %rax, (%rcx)

但是,如果使用struct A : X,则此函数为:

movl    (%rdx), %eax
movl    %eax, (%rcx)
movzwl  4(%rdx), %eax
movw    %ax, 4(%rcx)
movzbl  6(%rdx), %eax
movb    %al, 6(%rcx)

这两种情况实际上对应的尺寸分别是OP例子中的8 128 8

这样做的原因非常清楚:A可能被用作某些类B的基,然后调用func(b, a);必须小心不要干扰b的其他成员,这些成员可能位于填充区域(在OP的示例中为b.x8);

我在c++标准中看不到A : X的任何特殊属性,这将使g++决定填充在struct A : X中可重用,但在struct A中不可重用。AA : X都是可复制的标准布局POD

我猜这一定是一个基于典型使用的优化决策。没有重用的版本复制起来会更快。也许一个g++ ABI设计人员可以评论一下?

有趣的是,这个例子表明,简单的可复制并不意味着memcpy(&b, &a, sizeof b)等同于b = a !

我不是一个真正的c++语言律师,但是我目前发现的是:

参考这个问题的答案,一个结构体只保留一个标准布局POD,而在它自己和它的父类中只有一个类具有非静态成员。所以在这个想法下,A在两种情况下都有保证的布局,但B情况下都没有。

std::is_pod对A为真,对B为假。

  • 第一例:http://ideone.com/jyPb5J
  • 第二个案例:http://ideone.com/bYcLXa

所以,如果我自己正确地理解了这一点,编译器被允许在两种情况下用B的布局做它想做的事情。显然,在第二种情况下,它感觉像是利用了A的填充字节。