Visual C++中的结构对齐

9
Visual C++提供编译器开关(/Zp)和pack预处理指令来影响结构体成员的对齐方式。然而,我好像对它们的工作原理存在一些误解。
根据MSDN的说法,对于给定的对齐值n,
成员的对齐将在是n的倍数或成员大小的倍数中较小的一个上。
假设pack值为8字节(这是默认值),在结构体内,我认为任何大小小于8字节的成员都将位于其自身大小的倍数偏移量上。任何大小大于等于8字节的成员都将位于8字节的倍数偏移量上。
现在看下面的程序:
#include <tchar.h>

#pragma pack(8)

struct Foo {
    int i1;
    int i2;
    char c;
};

struct Bar {
    char c;
    Foo foo;
};

int _tmain(int argc, _TCHAR* argv[]) {
    int fooSize = sizeof(Foo); // yields 12
    Bar bar;
    int fooOffset = ((int) &bar.foo) - ((int) &bar); // yields 4

    return 0;
}

“Foo”结构体的大小为12个字节。因此,在“Bar”中,我希望“Foo”成员在偏移量8处(8的倍数),但实际上它位于偏移量4处。为什么会这样?
另外,“Foo”只有4+4+1=9个字节的数据。编译器会自动添加填充字节到结尾。但是,考虑到8字节对齐值,难道不应该填充到8的倍数而不是4吗?
任何澄清都将不胜感激!

你确定你的 int 只有4个字节吗?你在哪台机器上运行这个程序? - Tony The Lion
@Tony:这是一个32位应用程序。如果int不是4个字节而是8个字节,那么有两个这样的东西的Foo就不能只有12个字节了。 :-) - Daniel Wolf
3个回答

9
你的摘录解释了这个问题,“取两者中较小的值”。在32位平台上,一个int占用4字节。4比8要小。因此它有4字节的对齐。 pack编译指示会导致数据被打包,而不是解包。只有在需要时才会填充。

等一下。我有一个包含Foo成员的Bar结构体。因此我认为“成员的大小”是指Foo的大小,即12个字节。你是在说编译器实际上会/递归地/查找原始类型中最大的成员吗?如果是这样,那么有没有一些(在线)参考资料可以解释这种行为? - Daniel Wolf
1
编译器知道每种类型的对齐要求。Foo 的对齐需求是4。它不是最大的类型,但外部结构的要求通常是任何包含类型中最大的对齐要求,但并不总是如此。(它不能是完整类型的大小。否则,一个整数后面跟着一个9,000字节的结构将有近9000字节的填充。这很荒谬。MSDN 页面讨论的是对齐要求等于其大小的简单类型。) - David Schwartz
谢谢,这可以解释这种行为。关于你的论点,即它不能是完整的结构体大小:我认为这正是“较小者”规则的原因。你的 9,000 字节结构体,由于比 8 字节大,将被对齐到 8 的倍数,从而只浪费 4 字节而不是 8,996 字节。所以对我来说,MSDN 算法实际上是有意义的。可惜它似乎只是真相的一半! - Daniel Wolf
哦,我明白了。是的,这很有道理。但是没有必要把结构填充到比它的对齐要求更大的尺寸,因此编译器不会这样做,除非明确告知要进行填充。 - David Schwartz

6
记住为什么对齐在首位很重要。它的存在是为了让CPU快速读取内存而不必复用字节。 CPU从未一次性读取结构体,它只访问其成员。因此,Foo结构的大小为12字节是无关紧要的,只有其成员的对齐方式才重要。鉴于没有Foo成员需要大于4的对齐要求,因此Bar.foo成员只需对齐到4即可。
Foo大小为12字节而不是9字节也需要解释。编译器在结尾处添加3个字节的填充,以使Foo数组的每个元素的成员都正确对齐。

3

正如您的引用所说 - 要测试8字节对齐,您需要8个或更多字节的数据类型。这是一个带有一些明确大小类型的示例。另外,将小元素放在结构体末尾不会显示填充,因为它可以从结构体末尾删除。

#include <stdio.h>
int
main(int argc, char *argv[])
{
    struct S {
        __int64 a;
        __int8  b;
        __int64 c;
    };
#pragma pack(push,1)
    struct T {
        __int64 a;
        __int8  b;
        __int64 c;
    };
#pragma pack(pop)
#pragma pack(push,8)
    struct U {
        __int64 a;
        __int8  b;
        __int64 c;
    };
    struct B {
        __int8 c;
        struct U s;
    };
#pragma pack(pop)

    printf("S %d T %d U %d B %d\n",
           sizeof(struct S), sizeof(struct T),
           sizeof(struct U), sizeof(struct B));
    return 0;
}

使用VC 2010编译此代码:

C:\src>cl -nologo -W3 -Od packing.c && packing.exe
packing.c
S 24 T 17 U 24 B 32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接