【发布时间】:2013-12-03 14:25:24
【问题描述】:
struct A
{
char c;
double d;
} a;
- 在 mingw32-gcc.exe 中:
sizeof a = 16 - 在 gcc 4.6.3(ubuntu) 中:
sizeof a = 12
为什么它们不同?我觉得应该是16,gcc4.6.3做了一些优化吗?
【问题讨论】:
-
不同编译器的结构封装不同。
struct A
{
char c;
double d;
} a;
sizeof a = 16
sizeof a = 12
为什么它们不同?我觉得应该是16,gcc4.6.3做了一些优化吗?
【问题讨论】:
如果需要,编译器可能会针对目标架构执行数据结构对齐。它可能纯粹是为了提高应用程序的运行时性能,或者在某些情况下是处理器需要的(即,如果数据未对齐,程序将无法工作)。
例如,大多数(但不是全部)SSE2 指令要求数据在 16 字节边界上对齐。简而言之,计算机内存中的所有内容都有一个地址。假设我们有一个简单的双精度数组,如下所示:
double data[256];
为了使用需要 16 字节对齐的 SSE2 指令,必须确保 &data[0] 的地址是 16 的倍数。
对齐要求因一种架构而异。在 x86_64 上,建议所有大于 16 字节的结构在 16 字节边界上对齐。一般来说,为了获得最佳性能,请按如下方式对齐数据:
有趣的是,大多数 x86_64 CPU 可以同时处理对齐和非对齐数据。但是,如果数据没有正确对齐,CPU 执行代码会明显变慢。
当编译器考虑到这一点时,它可能会隐式对齐结构的成员,这会影响其大小。例如,假设我们有这样的结构:
struct A {
char a;
int b;
};
假设 x86_64,int 的大小是 32 位或 4 字节。因此,建议始终将b 的地址设为4 的倍数。但因为a 字段大小只有1 个字节,所以这是不可能的。因此,编译器会在 a 和 b 之间隐式添加 3 个字节的填充:
struct A {
char a;
char __pad0[3]; /* This would be added by compiler,
without any field names - __pad0 is for
demonstration purposes */
int b;
};
编译器的工作方式不仅取决于编译器和架构,还取决于您传递给编译器的编译器设置(标志)。使用特殊的语言结构也可以影响此行为。例如,可以要求编译器不要使用packed 属性执行任何填充,如下所示:
struct A {
char a;
int b;
} __attribute__((packed));
在您的情况下,mingw32-gcc.exe 只是在 c 和 d 之间添加了 7 个字节,以在 8 字节边界上对齐 d。而 Ubuntu 上的 gcc 4.6.3 仅添加了 3 个以将 d 对齐在 4 字节边界上。
除非您正在执行一些优化,尝试使用特殊的扩展指令集,或者对您的数据结构有特定要求,否则我建议您不要依赖特定的编译器行为,并且始终假设不仅您的结构可能会被填充,它可能会在架构、编译器和/或不同的编译器版本之间得到不同的填充。否则,您需要使用编译器属性和设置半手动地确保数据对齐和结构大小,并确保它在您使用单元测试甚至static assertions 定位的所有编译器和平台上都能正常工作。
更多信息,请查看:
希望对您有所帮助。祝你好运!
如何最小化内边距:
让所有结构成员正确对齐并同时保持结构大小合理总是好的。考虑这 2 个结构变体,其成员重新排列(从现在开始假设 sizeof char、short、int、long、long long 分别为 1、2、4、4、8):
struct A
{
char a;
short b;
char c;
int d;
};
struct B
{
char a;
char c;
short b;
int d;
};
两种结构都应该保持相同的数据,但是 sizeof(struct A) 将是 12 个字节,而 sizeof(struct B) 将是 8 字节,因为 -消除隐式填充的完全成员顺序:
struct A
{
char a;
char __pad0[1]; // implicit compiler padding
short b;
char c;
char __pad1[3]; // implicit compiler padding
int d;
};
struct B // no implicit padding
{
char a;
char c;
short b;
int d;
};
随着成员数量的增加,重新排列结构成员可能容易出错。为了减少出错 - 把最长的放在开头,最短的放在最后:
struct B // no implicit padding
{
int d;
short b;
char a;
char c;
};
结构末尾的隐式填充:
根据您使用的编译器、设置、平台等,您可能会注意到编译器不仅会在结构成员之前添加填充,还会在末尾(即最后一个成员之后)添加填充。下面的结构:
struct abcd
{
long long a;
char b;
};
可能占用 12 或 16 个字节(最差的编译器会允许它为 9 个字节)。此填充可能很容易被忽略,但如果您的结构将是数组元素,则该填充非常重要。它将确保您在后续数组单元格/元素中的 a 成员也将正确对齐。
最终和随机的想法:
如果 - 在使用结构时 - 你遵循以下建议,它永远不会伤害(并且实际上可能会拯救)你:
【讨论】:
sizeof 为structs 返回的值不受任何 C 标准的强制。这取决于编译器和机器架构。
例如,在 4 字节边界上对齐数据成员可能是最佳选择:在这种情况下,char c 的有效打包大小将为 4 字节。
【讨论】:
c 从结构的第 0 个字节开始,自然大小为 1。但它最多打包为 4 个字节。所以下一个成员 d 从结构的第 3 个字节开始。它的自然大小为 8,是 4 的倍数,因此无需包装。因此总大小为 12。