为什么sizeof运算符返回的结构大小大于结构成员的总大小?
当前回答
这可能是由于字节对齐和填充,使得结构在您的平台上达到偶数字节(或单词)。例如,在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位)。上面的程序(在使用gcc的Linux上)将大小打印为4、8和4,其中最后一个结构填充为一个单词(在我的32位平台上为4 x 8位字节)。
oneInt=4
twoInts=8
someBits=4
其他回答
C语言为编译器提供了一些关于内存中结构元素位置的自由:
内存孔可能出现在任意两个组件之间以及最后一个组件之后。这是由于目标计算机上的某些类型的对象可能受到寻址边界的限制sizeof运算符的结果中包含“内存孔”大小。sizeof仅不包括灵活数组的大小,灵活数组在C/C中可用++该语言的一些实现允许您通过pragma和编译器选项控制结构的内存布局
C语言为程序员提供了结构中元素布局的一些保证:
编译器需要分配一系列增加内存地址的组件第一个组件的地址与结构的起始地址一致未命名的位字段可以包括在结构中,以实现相邻元素的所需地址对齐
与元素对齐相关的问题:
不同的计算机以不同的方式排列对象的边缘位字段宽度的不同限制计算机在如何存储一个字中的字节方面存在差异(Intel 80x86和Motorola 68000)
对齐方式:
结构所占据的体积被计算为这样的结构阵列中对齐的单个元素的大小。结构应以便下一个后续结构的第一个元素不违反对齐要求
p.s更多详细信息可在此处获得:“塞缪尔·p·哈比森,盖伊·L·斯蒂尔C A参考,(5.6.2-5.6.7)”
这是因为添加了填充以满足对齐约束。数据结构对齐会影响程序的性能和正确性:
未对齐的访问可能是一个硬错误(通常是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语句来更改结构对齐设置。
例如,如果您希望结构具有GCC的特定大小,请使用__attribute__((打包))。
在Windows上,使用带有/Zp选项的cl.exe编译器时,可以将对齐设置为一个字节。
通常,CPU更容易访问4(或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体系结构始终能够获取未对齐的地址。然而,它速度较慢,当未对齐与两个不同的缓存线重叠时,当对齐的访问只会逐出一个缓存线时,它会逐出两个缓存线。
有些架构实际上必须捕获未对齐的读写,而早期版本的ARM架构(演变成当今所有移动CPU的架构)。。。事实上,他们只是返回了这些错误的数据。(他们忽略了低位。)
最后,请注意缓存线可以任意大,编译器不会试图猜测这些缓存线,也不会做出空间与速度的权衡。相反,对齐决策是ABI的一部分,表示最终将均匀填充缓存行的最小对齐。
TL;DR:对齐很重要。
另请参见:
对于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语言保证了成员顺序,但在编写跨平台或跨编译器程序时,我不会指望它。