【问题标题】:Why are the values returned by sizeof() compiler dependent?为什么 sizeof() 编译器返回的值取决于?
【发布时间】: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做了一些优化吗?

【问题讨论】:

标签: c++ c sizeof


【解决方案1】:

如果需要,编译器可能会针对目标架构执行数据结构对齐。它可能纯粹是为了提高应用程序的运行时性能,或者在某些情况下是处理器需要的(即,如果数据未对齐,程序将无法工作)。

例如,大多数(但不是全部)SSE2 指令要求数据在 16 字节边界上对齐。简而言之,计算机内存中的所有内容都有一个地址。假设我们有一个简单的双精度数组,如下所示:

double data[256];

为了使用需要 16 字节对齐的 SSE2 指令,必须确保 &data[0] 的地址是 16 的倍数。

对齐要求因一种架构而异。在 x86_64 上,建议所有大于 16 字节的结构在 16 字节边界上对齐。一般来说,为了获得最佳性能,请按如下方式对齐数据:

  • 在任意地址对齐 8 位数据
  • 对齐 16 位数据以包含在对齐的四字节字中
  • 对齐 32 位数据,使其基地址为 4 的倍数
  • 对齐 64 位数据,使其基地址为 8 的倍数
  • 对齐 80 位数据,使其基地址为 16 的倍数
  • 对齐 128 位数据,使其基地址为 16 的倍数

有趣的是,大多数 x86_64 CPU 可以同时处理对齐和非对齐数据。但是,如果数据没有正确对齐,CPU 执行代码会明显变慢。

当编译器考虑到这一点时,它可能会隐式对齐结构的成员,这会影响其大小。例如,假设我们有这样的结构:

struct A {
    char a;
    int b;
};

假设 x86_64,int 的大小是 32 位或 4 字节。因此,建议始终将b 的地址设为4 的倍数。但因为a 字段大小只有1 个字节,所以这是不可能的。因此,编译器会在 ab 之间隐式添加 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 只是在 cd 之间添加了 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 成员也将正确对齐。

最终和随机的想法:

如果 - 在使用结构时 - 你遵循以下建议,它永远不会伤害(并且实际上可能会拯救)你:

  • 不要依赖编译器将结构成员与适当的填充交错。
  • 确保您的结构(如果在数组之外)与其最长成员所需的边界对齐。
  • 确保安排结构成员,使最长的放在第一位,最后一个成员最短。
  • 确保显式填充结构(如果需要),以便在创建结构数组时,每个结构成员都有正确的对齐方式。
  • 确保您的结构数组也正确对齐,因为虽然您的结构可能需要 8 字节对齐,但您的编译器可能会在 4 字节边界对齐您的数组。

【讨论】:

  • @Vlad Lazarenko:这是一个很好的答案,我给你 100 个代表点。不过需要等到明天。
  • @VladLazarenko:为了让你的答案更完整,你可能会说一些好的做法,例如程序员将结构体从最长到最短重新排列以减小整体结构大小并减少填充。
  • @Artur:这是个好主意。我的时间有点短,所以如果你能补充一下,那就太好了。我会把它变成一个社区维基。
  • @VladLazarenko:一个非常详细的答案!对于曾经对依赖事实感到好奇的每个人来说,这确实是一个急需的解释。 +1 :)
【解决方案2】:

sizeofstructs 返回的值不受任何 C 标准的强制。这取决于编译器和机器架构。

例如,在 4 字节边界上对齐数据成员可能是最佳选择:在这种情况下,char c 的有效打包大小将为 4 字节。

【讨论】:

  • 情况2,sizeof(a)=12 = 4+8,怎么会这样
  • 案例 2 有 4 个字节打包:c 从结构的第 0 个字节开始,自然大小为 1。但它最多打包为 4 个字节。所以下一个成员 d 从结构的第 3 个字节开始。它的自然大小为 8,是 4 的倍数,因此无需包装。因此总大小为 12。
  • 是的。 8 字节打包也很常见。再次,编译器选择。
猜你喜欢
  • 2018-03-21
  • 2013-02-17
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多