在C++中,与包含类型相比,数组如何对齐?

26
假设我有一个类型为T的变量,需要按N字节对齐。现在我声明了一个T类型的数组:
T array[size];

数组是否具有与类型 T 相同的对齐要求,或者它是否具有其他对齐要求?


2
我很好奇,这有什么关系吗? - Pubby
2
@Pubby:我正在审核代码,发现了一些关于数组和对齐要求的奇怪注释,我几乎确定那个注释是胡说八道,但还想验证一下。 - sharptooth
4个回答

21

是的,对齐要求必须相同。显然,T 的数组必须至少与单个 T 一样严格地对齐,否则其第一个成员将无法正确对齐。数组不能比其元素类型更严格地对齐的事实源于标准第8.3.4节,该节指出数组是连续分配的元素子对象。考虑以下数组的数组:

T a[2][size];

无论 size 的值是多少,在数组 a[0]a[1] 之间都不能有“额外”的填充,否则将违反连续分配的要求。
等价地,我们知道 (char*)&a[1] == (char*)&a[0] + sizeof(a[0]) 并且 sizeof(a[0]) == sizeof(T[size]) == size * sizeof(T)。由于这对于任何的 size 都成立,因此必须能够在适当对齐单个 T 对象的地址处放置一个 T 类型的数组(假设有足够的地址空间)。

据我所知,如果子数组的大小是对齐的倍数,则对齐方式可能与元素的对齐方式不同,因为不需要填充。 这条评论似乎是正确的答案。(http://stackoverflow.com/users/147192/matthieu-m) - Pubby
抱歉,此链接无法访问。 - Pubby
1
T[2] 可以对齐到 alignof(T) * 2,这将允许 T[2][size] 不需要任何填充。 - Pubby
@Pubby:所以基本上你是说我的论点是错误的,因为一些数组可能比其他数组有更严格的对齐要求? - CB Bailey
2
使用“连续分配”要求和多维数组是一个有趣的论点;这从未发生在我身上。我总是对这种间接的论点有些怀疑,但在这种情况下,我认为这并没有什么区别:我怀疑如果委员会没有明确禁止要求更严格的对齐方式,那可能是因为他们没有想到这种情况可能存在。 - James Kanze
1
当应用于一个类时,结果是该类对象中的字节数,包括放置该类型对象在数组中所需的任何填充。§ 5.3.3/2 Sizeof n4606 - Michael Marcin

4
我相信数组的对齐需求将与数组元素的对齐需求完全相同。
显然,数组的开头必须至少与其第一个元素所需的一样严格对齐,因此它的对齐要求不能更不严格。
数组的起始地址加上每个元素的大小必须使第二个元素保持足够对齐。这会对元素类型的大小产生限制,我认为这意味着即使您从未在数组中使用该结构体,也可以在结构体末尾引入填充以仅保持数组对齐。但这并不意味着需要更严格的对齐要求。
通过归纳法,如果前两个元素是OK的,那么后续元素也是OK的,因此将数组与其元素具有相同的对齐要求应该是可以的。
不过,从规范中引用一下更好。

8
这里是(用于对齐)§5.3.6 [expr.alignof] 1/ alignof表达式产生其操作数类型的对齐要求。操作数应为表示完整对象类型或其数组的type-id [...] 3/ [...] 当将alignof应用于数组类型时,结果应为元素类型的对齐方式。 - Matthieu M.
任何“填充”都必须反映在 sizeof 中;不在数组中的元素必须与不在数组中的元素大小相同。至于第二点,我认为标准并不否认数组具有比非数组类型更严格的对齐要求的权利(我相当确定关于 C++03,它本身并没有太多关于对齐的规定),但实际上,对齐要求将是相同的。 - James Kanze
对齐要求将是相同的,但有一个明显的例外。这个new char[n](其中char实际上可以是任何字符类型)的返回值必须足够对齐于任何可以适配于n的类型。请注意,这个保证仅适用于new表达式;它不适用于类型为char[]的变量。 - James Kanze

1

我相信规则是一样的,但解释可能会令人困惑。

我认为由于数组的每个元素大小相同,因此仅对齐第一个元素就会自动对齐其余元素,因此元素之间永远不会有任何填充。

这在简单数组的情况下可能是正确的,但对于复杂情况则不然。

数组的步幅可以大于元素大小,即每个单独元素之间可能存在填充。

以下是一个很好的例子。

struct ThreeBytesWide {
    char a[3];
};

struct ThreeBytesWide myArray[100];

来源 - Stride 维基百科

ThreeBytesWide 数组的每个元素都可以对齐到四字节边界。

编辑:正如评论中所阐述的那样,提到在单个元素之间有填充时,是指元素本身为3字节且对齐到四字节边界。


那篇文章在C/C++行为方面的来源上是误导性的。从它的讨论页面可以看出:“在C和C++中,“stride”和sizeof()始终相同,即sizeof(T[N]) == sizeof(T) * N。例如,参见ISO14882:2003(e)的8.3.4或ISO9899:1999(e)的6.5.2.1以及ISO9899:1999(e)的6.5.6和ISO9899:1999(e)的6.2.5-20。此外,ISO9899:1999(e)的6.5.3.4-6在这里也很有趣。” - Jamey Sharp
数组中没有填充。如果结构体在四字节边界对齐,则每个结构体末尾也会有一个未使用的字节,因此sizeof(ThreeBytesWide)== 4 - Bo Persson
我不确定我完全理解两个评论。@BoPersson 我也在说 ThreeBytesWide 的 sizeof 将是 4。因此,对于单个数组,除了第一个字节外,不会有填充,除非像上面那样打包在结构体中。 - fkl
2
@fayyazkl - 抗议是针对“每个元素之间可能存在填充”。在数组成员之间永远不可能有填充,它们始终恰好相隔 sizeof(element)。如果有任何填充,它必须在每个数组元素内部而不是它们之间。 - Bo Persson
1
@BoPersson 再次审查我上面的答案,我感觉我写的是“数组元素之间永远不可能有填充。我展示的例子可能会让人误以为情况不同,但实际上每个结构体元素内部已经有了填充。对于数组来说,它仍然是每个元素大小为4字节。”我正在考虑改进我的措辞,因为我看到的与上面基本相同。 - fkl

0

需要一个对象数组是连续的,因此对象之间永远没有填充,尽管可以在对象的末尾添加填充(产生几乎相同的效果)。 C++数据成员对齐和数组打包

#include <iostream>

__declspec(align(32)) 
struct Str1
{
   int a;
   char c;
}; 

template<typename T>
struct size
{
    T arr[10];
};


int main()
{
    size<Str1> b1;

    std::cout << sizeof(Str1) << std::endl; //  prints 32
    std::cout << sizeof(b1) << std::endl;   //  prints 320

    std::cin.ignore();
    return 0;
}

参考资料:

  1. C++中的数据对齐、标准和可移植性
  2. http://msdn.microsoft.com/zh-cn/library/83ythb65.aspx

这并没有提及数组本身的对齐方式,只是关于大小的说明。 - sharptooth

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