为什么structof sizeof不等于每个成员的sizeof总和?

时间:2008-09-23 04:24:48

标签: c++ c struct sizeof c++-faq

为什么sizeof运算符返回的结构大小大于结构成员的总大小?

12 个答案:

答案 0 :(得分:595)

这是因为添加了填充以满足对齐约束。 Data structure alignment会影响程序的性能和正确性:

  • 错误对齐访问可能是一个严重错误(通常是SIGBUS)。
  • 错误对齐的访问可能是软错误。
    • 在硬件中进行了更正,以适度降低性能。
    • 或者通过软件仿真进行纠正,以避免严重的性能下降。
    • 此外,原子性和其他并发保证可能会被破坏,从而导致细微的错误。

以下是使用x86处理器的典型设置(所有使用的32位和64位模式)的示例:

struct X
{
    short s; /* 2 bytes */
             /* 2 padding bytes */
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 3 padding bytes */
};

struct Y
{
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
    short s; /* 2 bytes */
};

struct Z
{
    int   i; /* 4 bytes */
    short s; /* 2 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
};

const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */

可以通过对齐排序成员来最小化结构的大小(按基本类型的大小排序就足够了)(如上例中的结构Z)。

重要说明:C和C ++标准都声明结构对齐是实现定义的。因此,每个编译器可能选择以不同方式对齐数据,从而导致不同且不兼容的数据布局。因此,在处理将由不同编译器使用的库时,了解编译器如何对齐数据非常重要。某些编译器具有命令行设置和/或特殊#pragma语句来更改结构对齐设置。

答案 1 :(得分:160)

打包和字节对齐,如C FAQ here中所述:

  

这是为了对齐。许多处理器无法访问2字节和4字节   数量(例如整数和长整数)如果它们被塞进去的话   每-其中单向的。

     

假设您有这种结构:

struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};
     

现在,您可能认为应该可以打包它   像这样结构到内存中:

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+
     

但是如果编译器安排,它在处理器上要容易得多   它是这样的:

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+
     

在打包版本中,请注意它至少有点难度   你和我看看b和c字段是如何环绕的?简而言之,   处理器也很难。因此,大多数编译器都会填充   结构(好像有额外的,不可见的字段),如下所示:

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+

答案 2 :(得分:23)

如果您希望结构具有GCC的特定大小,例如使用__attribute__((packed))

在Windows上,将cl.exe编译器与/Zp option一起使用时,可以将对齐设置为一个字节。

通常,CPU更容易访问4(或8)的倍数,具体取决于平台和编译器。

所以这基本上是一个对齐的问题。

您需要有充分的理由进行更改。

答案 3 :(得分:13)

这可能是由于字节对齐和填充,因此结构在平台上出现偶数个字节(或单词)。例如,在Linux上的C中,有以下3种结构:

#include "stdio.h"


struct oneInt {
  int x;
};

struct twoInts {
  int x;
  int y;
};

struct someBits {
  int x:2;
  int y:6;
};


int main (int argc, char** argv) {
  printf("oneInt=%zu\n",sizeof(struct oneInt));
  printf("twoInts=%zu\n",sizeof(struct twoInts));
  printf("someBits=%zu\n",sizeof(struct someBits));
  return 0;
}

具有大小(以字节为单位)的成员分别是4字节(32位),8字节(2x 32位)和1字节(2 + 6位)。上面的程序(在Linux上使用gcc)将大小打印为4,8和4 - 其中最后一个结构被填充,因此它是一个单词(在我的32位平台上为4 x 8位字节)。

oneInt=4
twoInts=8
someBits=4

答案 4 :(得分:9)

另见:

for Microsoft Visual C:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

和GCC声称与Microsoft的编译器兼容。:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

除了之前的答案,请注意,无论包装是什么,在C ++中都没有成员订单保证。编译器可以(当然也可以)将虚拟表指针和基础结构的成员添加到结构中。标准不能确保虚拟表的存在(没有指定虚拟机制实现),因此可以得出结论,这种保证是不可能的。

我非常确定C 中成员订单 有保证,但在编写跨平台或交叉编译程序时我不会指望它

答案 5 :(得分:6)

由于所谓的包装,结构的大小大于其各部分的总和。特定处理器具有与其一起使用的优选数据大小。大多数现代处理器的首选大小,如果是32位(4字节)。当数据在这种边界上时访问内存比跨越该大小边界的内容更有效。

例如。考虑一下简单的结构:

struct myStruct
{
   int a;
   char b;
   int c;
} data;

如果机器是32位机器并且数据在32位边界上对齐,我们会立即看到问题(假设没有结构对齐)。在这个例子中,让我们假设结构数据从地址1024开始(0x400 - 注意最低的2位为零,因此数据与32位边界对齐)。对data.a的访问将正常工作,因为它从边界开始 - 0x400。对data.b的访问也可以正常工作,因为它位于地址0x404 - 另一个32位边界。但是未对齐的结构会将data.c放在地址0x405处。 data.c的4个字节位于0x405,0x406,0x407,0x408。在32位机器上,系统将在一个存储器周期内读取data.c,但只能获得4个字节中的3个(第4个字节位于下一个边界)。因此,系统必须进行第二次内存访问才能获得第4个字节,

现在,如果不是将data.c放在地址0x405,编译器将结构填充3个字节并将data.c放在地址0x408,那么系统只需要1个周期来读取数据,减少访问时间该数据元素减少了50%。填充交换内存效率以提高处理效率。鉴于计算机可以拥有大量内存(许多千兆字节),编译器认为交换(速度超过大小)是合理的。

不幸的是,当您尝试通过网络发送结构甚至将二进制数据写入二进制文件时,此问题将成为杀手锏。在结构或类的元素之间插入的填充可以破坏发送到文件或网络的数据。为了编写可移植代码(可以访问几个不同的编译器),您可能需要分别访问结构的每个元素以确保正确的“打包”。

另一方面,不同的编译器具有不同的管理数据结构打包的能力。例如,在Visual C / C ++中,编译器支持#pragma pack命令。这将允许您调整数据打包和对齐。

例如:

#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

我现在应该有11的长度。没有编译指示,我可以是11到14之间的任何东西(对于某些系统,多达32个),具体取决于编译器的默认包装。

答案 6 :(得分:5)

如果您隐式或显式设置结构的对齐方式,则可以这样做。对齐4的结构将始终是4个字节的倍数,即使其成员的大小不是4个字节的倍数。

同样可以在x86下使用32位整数编译库,如果您手动执行此操作,您可能会在64位进程上比较其组件会产生不同的结果。

答案 7 :(得分:5)

C99 N1256标准草案

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 sizeof运算符

  

3当应用于具有结构或联合类型的操作数时,   结果是这样一个对象的总字节数,   包括内部和尾部填充。

6.7.2.1结构和联合说明符

  13 p ...可能没有命名   在结构对象中填充,但不在其开头。

  

15结构或联合的末尾可能有未命名的填充。

新的C99 flexible array member featurestruct S {int is[];};)也可能会影响填充:

  

16作为一种特殊情况,具有多个命名成员的结构的最后一个元素可以   有一个不完整的数组类型;这被称为灵活的阵列成员。在大多数情况下,   灵活的数组成员被忽略。特别是,结构的大小就像是   省略了灵活的数组成员,除了它可能有更多的尾随填充   遗漏意味着。

附件J可移植性问题重申:

  

以下未指定:...

     
      
  • 在结构或联合中存储值时填充字节的值(6.2.6.1)
  •   

C ++ 11 N3337标准草案

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Sizeof

  

2应用时   对于一个类,结果是该类对象中的字节数,包括所需的任何填充   将该类型的对象放在数组中。

9.2班级成员

  

指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向它   初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。 [ 注意:   因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,   必要时,以实现适当的对齐。 - 结束说明]

我只知道足够的C ++来理解这个注释: - )

答案 8 :(得分:4)

除了其他答案之外,结构可以(但通常不是)具有虚函数,在这种情况下结构的大小也将包括vtbl的空间。

答案 9 :(得分:3)

C语言让编译器对内存中结构元素的位置有一些自由:

  • 内存孔可能出现在任何两个组件之间,也可能出现在最后一个组件之后。这是因为目标计算机上的某些类型的对象可能受到寻址边界的限制
  • “内存孔”大小包含在sizeof运算符的结果中。 sizeof仅包括灵活数组的大小(可在C / C ++中找到)
  • 该语言的某些实现允许您通过编译指示和编译器选项来控制结构的内存布局

C语言为程序员提供了结构中元素布局的一些保证:

  • 编译器需要分配一系列增加内存地址的组件
  • 第一个组件的地址与结构的起始地址一致
  • 未命名的位字段可以包含在结构中,以包含相邻元素所需的地址对齐

与元素对齐相关的问题:

  • 不同的计算机以不同的方式排列对象的边缘
  • 对位域宽度的不同限制
  • 计算机在如何将字节存储在一个字(英特尔80x86和摩托罗拉68000)
  • 方面存在差异

对齐的工作原理:

  • 结构占据的体积计算为这种结构阵列的对齐单个元素的大小。结构应该 结束,以便下一个结构的第一个元素不违反对齐要求

p.s更多详细信息请点击此处:“Samuel P.Harbison,Guy L.Steele C A Reference,(5.6.2 - 5.6.7)”

答案 10 :(得分:2)

对于速度和缓存考虑因素,应该从与其自然大小对齐的地址读取操作数。为了实现这一点,编译器将填充结构成员,以便对齐以下成员或后续结构。

struct pixel {
    unsigned char red;   // 0
    unsigned char green; // 1
    unsigned int alpha;  // 4 (gotta skip to an aligned offset)
    unsigned char blue;  // 8 (then skip 9 10 11)
};

// next offset: 12

x86架构始终能够获取未对齐的地址。但是,它的速度较慢,当错位与两个不同的缓存行重叠时,当对齐访问只会驱逐一个时,它会驱逐两个缓存行。

有些架构实际上必须捕获未对齐的读写,以及ARM架构的早期版本(演变成当今所有移动CPU的版本)......好吧,它们实际上只是返回了不良数据。 (他们忽略了低位。)

最后,请注意缓存行可以任意大,并且编译器不会尝试猜测它们或进行空间与速度的权衡。相反,对齐决策是ABI的一部分,表示最终将均匀填充缓存行的最小对齐。

TL; DR:对齐很重要。

答案 11 :(得分:0)

在其他有关内存对齐和结构填充/打包的解释清楚的答案中,通过仔细阅读,我在问题本身中发现了一些东西。

为什么sizeof的结构不等于每个成员的sizeof的总和?

为什么sizeof运算符返回的结构尺寸大于结构成员的总尺寸”?

两个问题都暗示了什么是明显的错误。至少在通用的,非示例性的视图中,就是这种情况。

应用于结构对象sizeof操作数的结果可以等于分别应用于每个成员的sizeof的总和。 不必更大/不同。

如果没有填充的理由,则不会填充任何内存。


如果结构仅包含相同类型的成员,则是大多数实现:

struct foo {
   int a;   
   int b;
   int c;     
} bar;

假设sizeof(int) == 4,结构bar的大小将等于所有成员在一起的大小之和,sizeof(bar) == 12。这里没有填充。

例如此处相同:

struct foo {
   short int a;   
   short int b;
   int c;     
} bar;

假设sizeof(short int) == 2sizeof(int) == 4ab的已分配字节总和等于最大成员c的已分配字节,并且所有内容都已完全对齐。因此,sizeof(bar) == 8

这也是关于结构填充的第二个最普遍的问题的对象,