为什么不是一个结构的sizeof等于每个成员的sizeof的总和?
为什么'sizeof'操作符返回的结构体积比结构体的总尺寸要大?
这是因为添加了填充以满足alignment约束。 数据结构调整影响程序的性能和正确性:
- 错误alignment的访问可能是一个硬性错误(通常是
SIGBUS
)。 - 错误alignment访问可能是一个软错误。
- 要么在硬件中纠正,要适度降低性能。
- 或者通过软件仿真来纠正,以降低性能。
- 另外,primefaces性和其他并发性保证可能被破坏,导致微妙的错误。
以下是使用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 */
可以通过alignmentsorting构件来最小化结构的大小(按照基本types对大小进行sorting就足够了)(如上例中的结构Z
)。
重要提示:C和C ++标准都声明结构alignment是实现定义的。 因此,每个编译器可能会select以不同方式alignment数据,导致不同的和不兼容的数据布局。 因此,在处理不同编译器使用的库时,理解编译器如何alignment数据非常重要。 一些编译器具有命令行设置和/或特殊的#pragma
语句来更改结构alignment设置。
打包和字节alignment,如C FAQ中所述:
这是为了alignment。 许多处理器无法访问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 | +-------+-------+-------+-------+
如果你想要结构与GCC有一定的大小,例如使用__attribute__((packed))
。
在Windows上,使用带有/ Zp选项的cl.exe编译器时,可以将alignment方式设置为一个字节。
通常,CPU可以更容易地访问4(或8)倍数的数据,这取决于平台和编译器。
所以这基本上是一个alignment的问题。
你需要有很好的理由来改变它。
这可能是由于字节alignment和填充,使得结构在您的平台上出现偶数个字节(或字)。 例如在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个字节(2个32位)和1个字节(2个6位)。 上面的程序(在使用gcc的Linux上)打印大小为4,8和4 – 最后一个结构被填充,以便它是一个单词(32位平台上的4 x 8位字节)。
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_002dPacking-Pragmas.html
除了以前的答案,请注意,不pipe是什么包装, C ++都没有成员订单保证 。 编译器可以(当然也可以)将虚拟表指针和基础结构的成员添加到结构中。 即使虚拟表格的存在不是由标准来保证的(没有规定虚拟机制的实现),因此可以得出这样的保证是不可能的。
我非常确定C语言中的成员顺序是有保证的 ,但在编写跨平台或交叉编译器程序时,我不会指望它。
它可以这样做,如果你有隐式或显式设置结构的alignment方式。 即使其成员的大小不是4字节的倍数,alignment4的结构也总是4字节的倍数。
另外一个库可以在x86下编译为32位整数,你可以在64位进程上比较它的组件,如果你手动完成这个工作,会给你一个不同的结果。
C99 N1256标准草案
JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4运营商的规模 :
3当应用于具有结构或联合types的操作数时,结果是此类对象中的总字节数,包括内部和尾部填充。
6.7.2.1结构和联合说明符 :
13 …结构对象中可能有未命名的填充,但不在其开头。
和:
15在结构或联盟的末尾可能有未命名的填充。
新的C99 灵活数组成员特性 ( struct S {int is[];};
)也可能影响填充:
作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组types; 这被称为灵活的数组成员。 在大多数情况下,灵活的数组成员被忽略。 特别是,结构的大小就好像是柔性arrays成员被省略,除了它可能具有比省略暗示的更多的尾部填充。
附件J可移植性问题重申:
以下是未指定的:…
- 将值存储在结构体或联合体中时填充字节的值(6.2.6.1)
C ++ 11 N3337标准草案
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3尺寸 :
2应用于类时,结果是该类的对象中的字节数,包括将该types的对象放入数组所需的任何填充。
9.2class级成员 :
指向标准布局结构对象的指针(使用reinterpret_cast适当转换)指向其初始成员(或者如果该成员是位字段,则指向其驻留的单元),反之亦然。 [注意:因此,在标准布局结构对象中可能有未命名的填充,但是在开始时并不需要填充,以实现适当的alignment。 – 结束注意]
我只知道足够的C ++来理解注释:-)
除了其他的答案,一个结构体可以(但通常不)具有虚函数,在这种情况下,结构体的大小也会包含vtbl的空间。
由于所谓的包装,结构的尺寸大于其部件的总和。 一个特定的处理器有一个适用的首选数据大小。 大多数现代处理器的首选大小,如果32位(4字节)。 当数据在这种边界上时访问内存比跨越该边界的东西更有效率。
例如。 考虑简单的结构:
struct myStruct { int a; char b; int c; } data;
如果机器是32位机器,并且数据在32位边界上alignment,我们会看到一个直接的问题(假设没有结构alignment)。 在这个例子中,让我们假设结构数据从地址1024(0x400开始 – 注意最低2位是零,所以数据alignment到32位边界)。 访问data.a将正常工作,因为它在边界上开始 – 0x400。 对data.b的访问也将正常工作,因为它位于地址0x404 – 另一个32位边界。 但是一个不alignment的结构会把data.c放在地址0x405处。 data.c的4个字节在0x405,0x406,0x407,0x408。 在一个32位的机器上,系统会在一个存储周期内读取data.c,但只能得到4个字节中的3个(第四个字节在下一个边界)。 所以,系统将不得不做第二次内存访问来获得第四个字节,
现在,如果不把data.c放在地址0x405处,编译器将结构填充3个字节,并把data.c放在地址0x408,那么系统只需要1个周期就可以读取数据,从而减less对该数据元素的访问时间减less了50%。 填充交换内存效率的处理效率。 鉴于计算机可能具有大量的内存(很多千兆字节),编译器认为交换(速度超过大小)是合理的。
不幸的是,当你试图通过networking发送结构,甚至把二进制数据写入二进制文件时,这个问题就变成了一个杀手。 插入结构或类的元素之间的填充可能会中断发送到文件或networking的数据。 为了编写可移植的代码(一个会去几个不同的编译器),你可能需要分别访问结构的每个元素,以确保正确的“打包”。
另一方面,不同的编译器具有不同的数据结构打包pipe理能力。 例如,在Visual C / C ++中,编译器支持#pragma pack命令。 这将允许您调整数据打包和alignment。
例如:
#pragma pack 1 struct MyStruct { int a; char b; int c; short d; } myData; I = sizeof(myData);
我现在应该有11的长度。没有编译指示,我可以是从11到14(对于某些系统,多达32)的任何东西,这取决于编译器的默认包装。
C语言为编译器提供了一些有关内存中结构元素位置的自由:
- 内存孔可能出现在任何两个组件之间,最后一个组件之后。 这是由于目标计算机上的某些types的对象可能受到寻址边界的限制
- 包含在sizeof运算符的结果中的“记忆孔”大小。 sizeof只包含灵活数组的大小,这在C / C ++中是可用的
- 该语言的某些实现允许您通过编译指示和编译器选项来控制结构的内存布局
C语言为程序员提供了结构中元素布局的一些保证:
- 编译器需要分配一系列增加内存地址的组件
- 第一个组件的地址与结构的起始地址一致
- 未命名的比特字段可以被包括在相邻元素的所需地址alignment的结构中
有关元素alignment的问题:
- 不同的计算机以不同的方式排列对象的边缘
- 位字段宽度的不同限制
- 计算机在如何存储字节(Intel 80×86和Motorola 68000)方面有所不同,
如何alignment工作:
- 该结构所占据的体积计算为这种结构arrays的alignment单个元素的大小。 结构应该结束,以便下一个结构的第一个元素不违反alignment的要求
ps更详细的信息可以在这里find:“Samuel P.Harbison,Guy L.Steele CA Reference,(5.6.2-5.6.7)”
这个想法是,为了速度和高速caching的考虑,操作数应该从地址alignment到自然大小。 为了做到这一点,编译器会填充结构成员,以便以下成员或以下结构alignment。
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架构一直能够获取未alignment的地址。 但是速度较慢,当错位重叠两个不同的高速caching行时,当一个alignment的访问只能驱逐两个高速caching行时,它会驱逐两个高速caching行。
一些体系结构实际上不得不陷入错位的读写操作,以及早期版本的ARM体系结构(演变成当今所有的移动CPU)……实际上,它们只是为那些返回了不好的数据。 (他们忽略了低位。)
最后,请注意,高速caching行可以是任意大的,编译器不会试图猜测这些行或者进行空间与速度的折衷。 相反,alignment决定是ABI的一部分,代表最终alignment,最终将平均填满caching行。
TL; DR:alignment很重要。