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

Why isn't sizeof for a struct equal to the sum of sizeof of each member?

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


这是因为添加了填充以满足对齐约束。数据结构一致性会影响程序的性能和正确性:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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语句来更改结构对齐设置。


打包和字节对齐,如这里的C FAQ所述:

It's for alignment. Many processors can't access 2- and 4-byte
quantities (e.g. ints and long ints) if they're crammed in
every-which-way.

Suppose you have this structure:

1
2
3
4
5
6
struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};

Now, you might think that it ought to be possible to pack this
structure into memory like this:

1
2
3
4
5
6
7
+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+

But it's much, much easier on the processor if the compiler arranges
it like this:

1
2
3
4
5
6
7
8
9
+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+

In the packed version, notice how it's at least a little bit hard for
you and me to see how the b and c fields wrap around? In a nutshell,
it's hard for the processor, too. Therefore, most compilers will pad
the structure (as if with extra, invisible fields) like this:

1
2
3
4
5
6
7
8
9
+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+


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

在Windows上,当使用cl.exe compier和/zp选项时,可以将对齐设置为一个字节。

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

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

你需要有充分的理由来改变它。


这可能是由于字节对齐和填充导致的,这样您的平台上的结构就可以得到偶数个字节(或字)。例如,在Linux上的C中,有以下3种结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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
"
,sizeof(struct oneInt));
  printf("twoInts=%zu
"
,sizeof(struct twoInts));
  printf("someBits=%zu
"
,sizeof(struct someBits));
  return 0;
}

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

1
2
3
oneInt=4
twoInts=8
someBits=4


参见:

对于Microsoft Visual C:

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

GCC声称与微软的编译器兼容。

http://gcc.gnu.org/onlinedocs/gcc/structure dpacking-pragmas.html

除了以前的答案,请注意,无论包装,没有会员订单保证在C++中。编译器可以(当然也可以)向结构中添加虚拟表指针和基结构的成员。即使是虚拟表的存在也不能通过标准来保证(没有规定虚拟机制的实现),因此可以得出这样的保证是不可能的。

我很确定C语言中的成员顺序是有保证的,但在编写跨平台或跨编译器程序时,我不会指望它。


由于所谓的包装,结构的尺寸大于其各部分的总和。一个特定的处理器有一个它所使用的首选数据大小。大多数现代处理器的首选大小是32位(4字节)。当数据位于此类边界上时,访问内存比跨越该大小边界的内容更有效。

例如。考虑简单的结构:

1
2
3
4
5
6
struct myStruct
{
   int a;
   char b;
   int c;
} data;

如果机器是32位机器,并且数据在32位边界上对齐,那么我们会看到一个即时问题(假设没有结构对齐)。在本例中,假设结构数据从地址1024(0x400)开始,注意最低的2位是零,因此数据与32位边界对齐。对数据的访问。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%。填充将内存效率与处理效率互换。考虑到计算机可以有大量的内存(许多千兆字节),编译器认为交换(速度超过大小)是合理的。

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

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

例如:

1
2
3
4
5
6
7
8
9
10
#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

我现在应该有11岁了。没有pragma,我可以是从11到14的任何东西(对于某些系统,最多32个),这取决于编译器的默认包装。


n1256 C99标准草案

JTC1关于http:/ / / / / sc22 www.open-std.org wg14 / www /文档/ n1256.pdf

sizeof操作符的:部分6.5.3.4

3 When applied to an operand that has structure or union type,
the result is the total number of bytes in such an object,
including internal and trailing padding.

6.7.2.1 specifiers:结构与联合

13 ... There may be unnamed
padding within a structure object, but not at its beginning.

和:

15 There may be unnamed padding at the end of a structure or union.

C99灵活数组成员的新特征(struct S {int is[];};)也可能影响填充:

16 As a special case, the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member. In most situations,
the flexible array member is ignored. In particular, the size of the structure is as if the
flexible array member were omitted except that it may have more trailing padding than
the omission would imply.

附录J:可移植性问题reiterates

The following are unspecified: ...

  • The value of padding bytes when storing values in structures or unions (6.2.6.1)

11 n3337 C++标准草案

JTC1关于http:/ / / / / wg21 www.open-std.org sc22 /文档/文件/ n3337.pdf 2012号

5.3.3 sizeof:

2 When applied
to a class, the result is the number of bytes in an object of that class including any padding required for
placing objects of that type in an array.

9.2级的成员。

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its
initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [ Note:
There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning,
as necessary to achieve appropriate alignment. — end note ]

我只知道我注意到在C + +:-)


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

另外,一个库可能是在x86下用32位整数编译的,并且您可能在64位进程上比较它的组件,如果您手工操作的话,会得到不同的结果。


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


关于C语言的编译器的一些自由叶片的结构元素的位置在内存:

  • 之间的任何两个孔可能appear存储器元件,和在最后的组件。事实上,这是由于某些类型的对象在目标计算机有限公司(可能是由边界寻址
  • "记忆中的孔的大小的结果,包括"sizeof操作符。"不是sizeof只包括柔性数组的大小,这是可用在C / C + +
  • 一些实现允许你控制的语言结构的内存布局和编译器选项编译通过

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

  • 编译器需要assign系列的组件增加内存地址
  • 第一部分coincides地址的起始地址的结构
  • unnamed位字段可能包含在结构的要求alignments)相邻元素的地址

相关问题:元素的对齐

  • 不同的计算机对象的边缘线),在不同的方式
  • 不同宽度的限制上的位场
  • 如何在不同的计算机中存储的字节字(Intel 80x86和摩托罗拉68000)

如何:向厂

  • 自用的结构体积的大小是计算的单元阵列对准一个这样的结构。这样的结构这样,在第一次结束后的下一个元素的violate结构不要求校准

还有更多的详细信息是可用的在这里:"Samuel P. Harbison,Guy L. Steele(2 - C的参考,5.6.7)"


的想法是,速度和缓存的考虑,应该是从地址读operands自然对准到它们的大小。这是发生在编译器结构,垫成员结构成员或以下的操作系统后,将自对准。

1
2
3
4
5
6
7
8
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体系结构misaligned一直能够读取的地址。然而,它是慢的和当misalignment重叠两个不同的高速缓存行的高速缓存行驱逐,然后它在一个一个的访问将只evict对准。

一些建筑实际上已经misaligned陷阱在线读取和写的,和早期版本的ARM体系结构(在一个进化到今天,所有的移动CPU)…………………好了,他们真的只是那些坏的数据被返回。(他们的ignored低阶位)。

最后,请注意,高速缓存行可以被编译arbitrarily大,并不是试图让那些想在欧tradeoff空间与速度。因此,该部分的决定是线形的线形,ABI和代表最低eventually evenly将高速缓存线填充起来。

热释光:线形是重要的医生。