为什么sizeof运算符返回的结构大小大于结构成员的总大小?
当前回答
打包和字节对齐,如C常见问题解答中所述:
这是为了对齐。许多处理器无法访问2字节和4字节数量(例如整数和长整数),如果它们被塞进每个方向。假设您有这样的结构:结构{字符a[3];短整数b;长整型c;字符d[3];};现在,你可能认为应该可以打包这个结构如下:+-------+-------+-------+-------+|a | b|+-------+-------+-------+-------+|b | c|+-------+-------+-------+-------+|c | d(c | d)|+-------+-------+-------+-------+但如果编译器安排,则处理器上的操作要简单得多它是这样的:+-------+-------+-------+|一个|+-------+-------+-------+|b级|+-------+-------+-------+-------+|c类|+-------+-------+-------+-------+|d)|+-------+-------+-------+在打包版本中,请注意,对于你和我想看看b和c字段是如何换行的?简而言之处理器也很难。因此,大多数编译器都会填充结构(好像有额外的、不可见的字段)如下:+-------+-------+-------+-------+|a |焊盘1|+-------+-------+-------+-------+|b |焊盘2|+-------+-------+-------+-------+|c类|+-------+-------+-------+-------+|d|焊盘3|+-------+-------+-------+-------+
其他回答
结构的尺寸大于其部件的总和,这是因为所谓的填充。一个特定的处理器有一个首选的数据大小。大多数现代处理器的首选大小为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。数据.c的4个字节位于0x405、0x406、0x407、0x408。在32位机器上,系统将在一个内存周期内读取data.c,但只能获得4个字节中的3个字节(第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。如果没有pragma,我可以从11到14(对于某些系统,可以多达32),这取决于编译器的默认打包。
除了其他答案,结构可以(但通常不)具有虚拟函数,在这种情况下,结构的大小还将包括vtbl的空间。
其思想是,出于速度和缓存的考虑,操作数应从与其自然大小对齐的地址中读取。为了实现这一点,编译器填充结构成员,以便对齐以下成员或以下结构。
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:对齐很重要。
C语言为编译器提供了一些关于内存中结构元素位置的自由:
内存孔可能出现在任意两个组件之间以及最后一个组件之后。这是由于目标计算机上的某些类型的对象可能受到寻址边界的限制sizeof运算符的结果中包含“内存孔”大小。sizeof仅不包括灵活数组的大小,灵活数组在C/C中可用++该语言的一些实现允许您通过pragma和编译器选项控制结构的内存布局
C语言为程序员提供了结构中元素布局的一些保证:
编译器需要分配一系列增加内存地址的组件第一个组件的地址与结构的起始地址一致未命名的位字段可以包括在结构中,以实现相邻元素的所需地址对齐
与元素对齐相关的问题:
不同的计算机以不同的方式排列对象的边缘位字段宽度的不同限制计算机在如何存储一个字中的字节方面存在差异(Intel 80x86和Motorola 68000)
对齐方式:
结构所占据的体积被计算为这样的结构阵列中对齐的单个元素的大小。结构应以便下一个后续结构的第一个元素不违反对齐要求
p.s更多详细信息可在此处获得:“塞缪尔·p·哈比森,盖伊·L·斯蒂尔C A参考,(5.6.2-5.6.7)”
另请参见:
对于Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
GCC声称与微软编译器兼容
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Structure_002dPacking-Pragmas.html
除了前面的答案,请注意,无论包装如何,C++中没有成员订单保证。编译器可以(当然也可以)向结构中添加虚拟表指针和基结构的成员。即使是虚拟表的存在也没有得到标准的保证(没有规定虚拟机制的实现),因此可以得出这样的保证是不可能的。
我很确定C语言保证了成员顺序,但在编写跨平台或跨编译器程序时,我不会指望它。