结构的大小是否必须是该结构的对齐的精确倍数?

时间:2011-01-09 05:02:57

标签: c++ struct alignment sizeof

我再一次质疑一个长期存在的信念。

直到今天,我相信以下结构的对齐通常为4,大小通常为5 ...

struct example
{
  int   m_Assume_32_Bits;
  char  m_Assume_8_Bit_Bytes;
};

由于这个假设,我有数据结构代码,它使用offsetof来确定数组中两个相邻项之间的字节距离。今天,我发现了一些使用sizeof的旧代码,它应该没有,无法理解为什么我没有错误,编写单元测试 - 测试让我感到惊讶。

一些调查显示我用于测试的类型的大小(类似于上面的结构)是对齐的精确倍数 - 即8个字节。它在最终成员之后有填充。以下是我从未预料到这一点的一个例子......

struct example2
{
  example m_Example;
  char    m_Why_Cant_This_Be_At_Offset_6_Bytes;
};

一些谷歌搜索显示的例子清楚地表明允许最后一个成员之后的填充 - 例如http://en.wikipedia.org/wiki/Data_structure_alignment#Data_structure_padding(“结构”或“结尾”位)。

这有点令人尴尬,因为我最近发表了这条评论 - Use of struct padding(我对该答案的第一个评论)。

我似乎无法确定的是,C ++标准是否保证了这种填充到对齐的精确倍数,或者它是否只是允许的内容以及某些(但可能不是全部)编译器所做的事情。

那么 - 根据C ++标准,结构的大小是否需要是该结构对齐的精确倍数?

如果C标准有不同的保证,我也对此感兴趣,但重点是C ++。

9 个答案:

答案 0 :(得分:17)

5.3.3 / 2

  

当应用于类时,[sizeof]的结果是该类对象中的字节数,包括在数组中放置该类型对象所需的任何填充。

所以是的,对象大小是其对齐的倍数。

答案 1 :(得分:9)

alignment 大小的一个定义:

  

结构的 alignment 大小是当你有一个该结构的数组时从一个元素到下一个元素的偏移量。

就其性质而言,如果你有一个包含两个元素的结构数组,那么两者都需要有对齐的成员,这意味着是的,大小必须是对齐的倍数。 (我不确定是否有任何标准明确强制执行此操作,但由于结构的大小和对齐方式不依赖于结构是单独还是数组内部,因此相同的规则适用于两者,因此它不能真正实现任何其他方式。)

答案 2 :(得分:6)

标准说明([dcl.array]部分:

  

数组类型的对象包含一个连续分配的非空N个子类型的T类型。

因此数组元素之间没有填充。

标准不要求在结构内部填充,但标准不允许任何其他方式对齐数组元素。

答案 3 :(得分:2)

我不确定这是否属于实际的C / C ++标准,我倾向于说这取决于编译器(只是为了安全起见)。然而,几个月前,我有一个“有趣”的时间来解决这个问题,我必须在网络中将动态生成的C结构作为字节数组作为协议的一部分发送,以便与芯片通信。所有结构的对齐和大小必须与芯片上运行的代码中的结构一致,该代码是使用GIPS架构的GCC变体编译的。我将尝试给出算法,应该应用于gcc的所有变体(并且希望大多数其他编译器)。

所有基本类型,例如字符 int 与其大小对齐,并且与下一个可用位置对齐,无论父母的对齐。回答原始问题,是的,总大小是对齐的倍数。

// size 8
struct {
    char A; //byte 0
    char B; //byte 1
    int C; //byte 4
};

即使结构的对齐是4个字节,字符仍然尽可能地打包。

结构的对齐等于其成员的最大对齐

示例:

//size 4, but alignment is 2!
struct foo {
    char A; //byte 0
    char B; //byte 1
    short C; //byte 3
}

//size 6
struct bar {
    char A;         //byte 0
    struct foo B;   //byte 2
}

这也适用于工会,并以一种奇怪的方式。联合的大小可以大于其成员的任何大小,这仅仅是因为对齐:

//size 3, alignment 1
struct foo {
    char A; //byte 0
    char B; //byte 1
    char C; //byte 2
};

//size 2, alignment 2
struct bar {
    short A; //byte 0
};

//size 4! alignment 2
union foobar {
    struct foo A;
    struct bar B;
}

使用这些简单的规则,能够找出您遇到的任何可怕的嵌套联合/结构的对齐/大小。这一切都来自记忆,所以如果我错过了一个无法根据这些规则决定的角落案件,请告诉我!

答案 4 :(得分:1)

所以将你的问题分成两部分:

<强> 1。这是合法的吗?

  

[5.3.3.2] 当应用于类时,sizeof()运算符的结果 [em]是该类对象中的字节数,包括所需的任何填充将该类型的对象放在数组中。

所以,不,不是。

<强> 2。那么,为什么不呢?

在这里,我只推测。

2.1。指针算术变得怪异
如果对齐将是“在数组元素之间”但不会影响大小,则zthigns将变得不必要地复杂化,例如。

(char *)(X+1) != ((char *)X) + sizeof(X)

(我有一种预感,即使没有上述声明,这也是标准所要求的,但我无法将其证明)

2.2简单
如果对齐影响大小,则可以通过查看单个类型来确定对齐和大小。考虑一下:

struct A  {  int x; char y;  }
struct B  { A left, right;   }

根据现行标准,我只需要知道尺寸(A)来确定B的尺寸和布局 使用备用,你建议我需要知道A的内部。类似于你的example2:对于“更好的打包”,sizeof(示例)是不够的,你需要考虑示例的内部。

答案 5 :(得分:1)

C ++没有明确说明,但它是另外两个要求的结果:

首先,所有对象必须良好对齐。

3.8 / 1说

  

类型为T的对象的生命周期始于[...] 存储且具有正确对齐且类型为T的大小

和3.9 / 5:

  

对象类型具有* alignnment要求(3.9.1,3.9.2)。完整对象类型的 alignment 是一个实现定义的整数值,表示字节数;在满足其对象类型的对齐要求的地址处分配对象。

因此,每个对象必须根据其对齐要求进行对齐。

另一个要求是数组中的对象是有争议的:

8.3.4 / 1:

  

数组类型的对象包含N类型的T子对象的连续分配的非空集。

对于要连续分配的数组中的对象,之间不能填充。但是要使数组中的每个对象都正确对齐,必须填充每个单独的对象,以便紧接在对象结束后的字节也很好地对齐。换句话说,对象的大小必须是其对齐的倍数。

答案 6 :(得分:1)

可以生成对齐方式不是其大小的倍数的C或C ++ typedef最近在this bindgen bug中出现过。这是一个最小的示例,在下面我将其称为test.c

#include <stdio.h>
#include <stdalign.h>

__attribute__ ((aligned(4))) typedef struct {
    char x[3];
} WeirdType;

int main() {
    printf("sizeof(WeirdType) = %ld\n", sizeof(WeirdType));
    printf("alignof(WeirdType) = %ld\n", alignof(WeirdType));
    return 0;
}

在我的Arch Linux x86_64计算机上,gcc -dumpversion && gcc test.c && ./a.out打印:

9.3.0
sizeof(WeirdType) = 3
alignof(WeirdType) = 4

类似clang -dumpversion && clang test.c && ./a.out打印:

9.0.1
sizeof(WeirdType) = 3
alignof(WeirdType) = 4

将文件另存为test.cc并使用g++ / clang++可获得相同的结果。

但是,值得注意的是,Windows上的MSVC似乎不会重现

答案 7 :(得分:0)

标准对填充和对齐几乎没有说明。保证很少。关于你唯一可以赌的是第一个元素是在结构的开头。之后......对齐和填充可以是任何东西。

答案 8 :(得分:0)

似乎C ++ 03标准没有说(或者我没有找到)对齐填充字节是否应该包含在对象表示中。

并且C99标准说“sizeof”结构类型或联合类型包括内部和尾部填充,但我不确定是否所有对齐填充都包含在“尾部填充”中。

现在回到你的例子。真的没有混乱。 sizeof(example) == 8表示该结构确实需要8个字节来表示自身,包括拖尾3个填充字节。如果第二个结构中的char的偏移量为6,则它将覆盖m_Example使用的空间。某种类型的布局是实现定义的,应该在整个实现中保持稳定。

不过,p+1等于(T*)((char*)p + sizeof(T))是否不确定。我希望能找到答案。