为什么不sizeof结构的总和等于sizeof的每个成员?
题
为什么 sizeof
操作者返回的尺寸大于一个结构比总的结构的成员?
解决方案
这是因为填补增加,以满足对准的约束。 数据结构对齐 影响两性和正确性的程序:
- Mis盟的访问可能是一个艰难的错误(经常
SIGBUS
). - Mis盟的访问可能是一个柔软的错误。
- 要么正在硬件、适度性下降。
- 或者修正通过模拟软件、为的严重性降解。
- 此外,原子性和其他并发性的保证可能被破坏,导致微妙的错误。
这里有一个例子使用的典型设置一个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
发言结构的改变对准设置。
其他提示
打包和字节对齐,如C FAQ中所述此处:
这是为了对齐。许多处理器无法访问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上,当使用cl.exe编译器和 / Zp选项。
通常,CPU更容易访问4(或8)的倍数,具体取决于平台和编译器。
所以这基本上是一个对齐的问题。
您需要有充分的理由进行更改。
这可能是由于字节对齐和填充,因此结构在平台上出现偶数个字节(或单词)。例如,在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
另见:
for Microsoft Visual C:
http://msdn.microsoft的.com / EN-US /库/ 2e70t5y1%28V = VS.80%29.aspx
和GCC声称与Microsoft的编译器兼容。:
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking -Pragmas.html
除了之前的答案,请注意,无论打包,在C ++中都没有成员订单保证。编译器可以(当然也可以)将虚拟表指针和基础结构的成员添加到结构中。标准不能确保虚拟表的存在(没有指定虚拟机制实现),因此可以得出结论,这种保证是不可能的。
我非常确定C 中成员订单 有保证,但在编写跨平台或交叉编译程序时我不会指望它
由于所谓的包装,结构的大小大于其各部分的总和。特定处理器具有与其一起使用的优选数据大小。大多数现代处理器的首选大小,如果是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个),具体取决于编译器的默认打包。
如果您隐式或显式设置结构的对齐方式,则可以这样做。对齐4的结构将始终是4个字节的倍数,即使其成员的大小不是4个字节的倍数。
同样可以在x86下使用32位整数编译库,如果您手动执行此操作,您可能会在64位进程上比较其组件会产生不同的结果。
C99N1256标准草案
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4的sizeof操作员:
3当适用于一个操作数,具有结构或联合型的, 结果是总的数字,在这样一个对象, 包括内部和尾部填充。
6.7.2.1结构和联合说明:
13...有可能是未命名的 填充的结构内的对象,而不是在其开始。
并且:
15有可能被匿名的填补在结束的一个结构或联盟。
新C99 灵活的阵成员的特征 (struct S {int is[];};
)也可能影响填充:
16作为一个特殊的情况下,最后一项要件的结构与超过一名的成员可以 有一个不完整的阵型;这就是所谓的灵活阵成员。在大多数情况下, 灵活的阵列的成员被忽略。特别是,结构的大小是因为如果 灵活的阵成员被省略,但它可能有更多的后的填补比 遗漏。
附件J携带问题 重申:
以下是未指定:...
- 值的填字节时储存值的结构或工会(6.2.6.1)
C++11N3337标准草案
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3Sizeof:
2应用时 一类,结果是数字节目的的该类包括任何填补需要 将该类型的对象在一个阵列。
9.2类成员:
一个指向标准布局结构的目的,适当地转换成使用reinterpret_cast,它的 初始成员(或者如果该成员是一位域,然后向部在其驻留),反之亦然。[注:有可能因此被命名的填补内的一个标准布局结构的对象,而不是在其开始, 如有必要实现适当的应对准。—结束注意]
我只知道足够C++理解该说明:-)
除了其他答案之外,结构可以(但通常不是)具有虚函数,在这种情况下结构的大小也将包括vtbl的空间。
C语言叶编译器的一些自由有关的位置结构元素存储器:
- 存储器孔之间可能出现的任何两个组成部分,并在最后的成分。这是由于这样的事实,某些类型的对象目标计算机上可能是有限的边界的解决
- "存储器孔"尺寸,包括在结果的sizeof操作员。该sizeof只不包括大小的灵活阵列,这是可以在C/C++
- 一些实施中的语言能让你到控制的存储器局的结构,通过编译和编译器的选择
C语言提供了一些保证的程序要素的布局的结构:
- 编译器所需的分配序列对组件增加存储器的地址
- 地址第一部分相吻合的开始,地址的结构
- 未命名的位的领域可包括在结构到要求的地址的路线相邻的元素
问题相关的要素一致性:
- 不同的计算机线边缘的对象,在不同的方式
- 不同限制的宽度点领域
- 计算机不同,在如何储存的字节中的一个词语(英特尔80条x86和摩托罗拉68000)
如何对准工作:
- 所占体积的计算结构的大小对准单元的一系列这样的结构。该结构应 结束这样的第一个元素的下一个结构并不违反要求对准
p.s更详细的信息:"塞缪尔*P*哈比森,家伙L.斯蒂尔C基准,(5.6.2-5.6.7)"
对于速度和缓存考虑因素,应该从与其自然大小对齐的地址读取操作数。为了实现这一点,编译器将填充结构成员,以便对齐以下成员或后续结构。
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:对齐很重要。