为什么编译器会抱怨对齐?

13

我想更多地了解对齐。为什么Microsoft编译器(Visual Studio 2012 Express)会抱怨以下代码片段的对齐方式?

__declspec(align(16)) class Foo
{
public:
    virtual ~Foo() {}
    virtual void bar() = 0;
};

这是编译器向我呈现的警告:

warning C4324: 'Foo' : structure was padded due to __declspec(align())

即使这个类没有任何虚方法,也并不重要。即使是一个空的类,编译器也会显示相同的警告信息。一个空类如何对齐?编译器如何填充这个类?


1
它的对齐方式是根据你明确告诉编译器的对齐方式——16字节对齐。这需要16字节的填充。如果没有这个,一个空结构体应该是1字节对齐,并且大小为1。空类是“特殊”的,因为标准要求没有完整的对象大小为0,所以如果一个类没有数据成员或基类,它总是需要一些填充,除非它有虚函数表。对齐要求意味着它需要更多的空间。 - Steve Jessop
尝试将警告级别降至4级,而不是选择“全部”。启用“全部”警告后,它会抱怨所有东西,包括你最喜欢的颜色和狗的名字。 - Mysticial
好的,但它似乎不是自然对齐方式。如果我没有明确告诉编译器以某种方式对齐它,我想了解一个空类如何对齐。 - Jan Deinhard
1
按照定义,类的对齐必须是其大小的因素,因此查看一个没有__declspec(align())的空类的大小。如果它是1,则必然对齐为1。此外,C++11有一个alignof运算符,因此如果您可以从Visual C++中获取该值,则可以消除所有疑虑。 - Steve Jessop
哦,等等,算了。我撤回我的评论(但不会真的删除它)。你收到的警告不是我通常在打开Wall时看到的结构填充警告。 - Mysticial
4个回答

20

警告并不一定意味着你做错了什么,而是告诉你你可能没有预期的行为。请注意,编译器可以对开发人员认为值得警告的任何事情发出警告。原则上,您也可能会收到有关在13号星期五编译的警告。

在这种特定情况下,假设可能是当您指定对齐方式时,您不希望使类变大。因此,如果由于您给出的对齐要求而使类变大,那么您犯了错误的可能性不小。

当然,这就引出了一个问题,即为什么对齐要求会使类变大。现在我们回到了标准领域(虽然__declspec本身是Microsoft扩展,而不是标准)。C++标准要求数组中的对象彼此紧密相连,之间没有任何空隙。因此,如果您的对象必须对齐到16字节边界,则该对象的大小必须是16的倍数。如果成员变量的大小(显式和隐式)不能达到所需的大小,编译器就必须向对象中添加未使用的字节。这些字节称为填充。请注意,即使是非数组成员对象也存在这些填充字节。

现在,您的类仅包含一个隐式虚拟指针(因为它包含虚拟函数),这个指针根据架构的不同可能是4或8个字节。由于您请求16字节对齐方式,编译器必须添加12或8个字节的填充以使大小成为16的倍数,而如果没有手动对齐规范,它就不必添加。这就是编译器发出警告的原因。


19
应该有一个“警告:你今天早上9点开始工作,现在已经晚上8点了。去吃点东西,你正在低血糖中迷糊,你的代码毫无意义。” - Steve Jessop

2
当您使用__declspec(align())alignas()增加结构体的对齐方式时,它不仅会使您的结构体更加严格地对齐,而且可能需要在结构体末尾进行填充,根据这个规则

一个结构体的大小是其对齐方式的最小倍数,大于或等于其最后一个成员的偏移量。

换句话说,如果您将结构体的对齐方式提高到16,您最好确保结构体的大小也是16的倍数,否则您的结构体由于填充而可能发生大小变化,这可能导致您的程序崩溃(编译器无法确定您依赖于结构体的大小以及您是否了解此规则,因此会发出警告)。

优秀的回答。谢谢。 - Alex Moreno

2
在x86架构中,Foo需要4个字节,因此需要12个字节的填充。而在x64架构中,Foo需要8个字节,因此只需要8个字节的填充。

2
这个警告的意思是,使用__declspec(align())会导致类的大小(由sizeof返回)发生改变(增加),这可能会破坏某些东西,因此会出现警告。
空类当然有一个大小,必须大于0,因此至少为1。
通常情况下,大小不会随着对齐而改变,但在您的情况下,它确实会发生变化,因为您指定的对齐方式大于类的未填充大小。请记住,在C中,类型的对齐方式不能小于其大小,实际上大小必须是对齐方式的倍数,因此为了匹配对齐方式,大小被增加。
为什么大小必须是对齐方式的倍数?好吧,想象一下这种类型的数组:连续的元素必须恰好相隔sizeof(T),但每个对象必须在对齐的内存地址中。解决这个方程式的唯一方法是sizeof(T)必须是对齐的倍数(非空)。

1
因为您指定的对齐方式大于类的未填充大小,所以出现了这种情况。这并不完全正确。一个有3个字符的对象,对齐到2,将会得到1个额外的字节填充。 - Mooing Duck
@MooingDuck:您当然是正确的。我的意思是:“如果所需对齐方式大于大小,则大小将增加”,而不是“仅当”。 - rodrigo

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