【问题标题】:sizeof(), alignment in C structs:sizeof(),C 结构中的对齐方式:
【发布时间】:2018-11-21 18:01:30
【问题描述】:

前言: 我对结构对齐进行了研究。看了this 问题,this 一个和this 一个 - 但仍然没有找到我的答案。

我的实际问题:

这是我创建的代码 sn-p 以澄清我的问题:

#include "stdafx.h"
#include <stdio.h>

struct IntAndCharStruct
{
    int a;
    char b;
};

struct IntAndDoubleStruct
{
    int a;
    double d;
};

struct IntFloatAndDoubleStruct
{
    int a;
    float c;
    double d;
};

int main()
{
    printf("Int: %d\n", sizeof(int));
    printf("Float: %d\n", sizeof(float));
    printf("Char: %d\n", sizeof(char));
    printf("Double: %d\n", sizeof(double));
    printf("IntAndCharStruct: %d\n", sizeof(IntAndCharStruct));
    printf("IntAndDoubleStruct: %d\n", sizeof(IntAndDoubleStruct));
    printf("IntFloatAndDoubleStruct: %d\n", sizeof(IntFloatAndDoubleStruct));
    getchar();
}

它的输出是:

Int: 4
Float: 4
Char: 1
Double: 8
IntAndCharStruct: 8
IntAndDoubleStruct: 16
IntFloatAndDoubleStruct: 16

我在IntAndCharStructIntAndDoubleStruct 中看到了对齐。

但我就是没有得到IntFloatAndDoubleStruct 一个

简单地说:为什么不是sizeof(IntFloatAndDoubleStruct) = 24

提前致谢!

p.s:我使用的是 Visual-Studio 2017,标准控制台应用程序。

编辑: 每个 cmets,测试 IntDoubleAndFloatStruct(元素的不同顺序)并在 sizeof() 中得到 24 个 - 如果答案也能说明并解释这种情况,我会很高兴。

【问题讨论】:

  • 切换float和double的顺序,你应该得到24。
  • 你为什么期望得到 24 个?如果没有double,您对intfloat 有什么期望,为什么?
  • @Gerhardh - 换了顺序,得到了 24!至于我的期望,我期望与最大元素对齐 - 所以 8 字节 * 3 个元素 = 24。Int 和 Float 具有相同的大小 (4),所以 8 就可以了 IMO。
  • 我喜欢你研究过的链接列表,而不是仅仅写“我阅读了所有内容”。就像许多其他问题一样。干得好。

标签: c struct alignment


【解决方案1】:

在您的平台上,以下成立:intfloat 的大小均为 4。double 的大小和对齐要求为 8。

我们从您显示的sizeof 输出中知道这一点。 sizeof (T) 给出数组中两个连续的 T 类型元素的地址之间的字节数。所以我们知道对齐要求就像我上面所说的那样。 (注)

现在,编译器为 IntFloatAndDoubleStruct 报告了 16。有效果吗?

假设我们有这样一个对象,其地址与 16 对齐。

  • int a 因此在地址 X 与 16 对齐,所以它与 4 对齐就好了。它将占用字节 [X, X+4)
  • 这意味着float c 可以从 X+4 开始,它与 4 对齐,这对于float 来说很好。它将占用字节 [X+4, X+8)
  • 最后,double d 可以从 X+8 开始,它与 8 对齐,这对double 来说很好。它将占用字节 [X+8, X+16)
  • 这使得 X+16 可用于下一个结构对象,再次与 16 对齐。

所以没有理由以后再启动任何成员,所以整个结构适合 16 个字节就好了。


(注) 严格来说这不是真的:对于每一个,我们知道大小和对齐都是

【讨论】:

  • 但是编译器不是将 all 元素对齐到相同的大小 (8) 吗?我认为“int”也应该与 8 对齐!
  • @MordechayS 为什么会这样? sizeof 有效地告诉您对齐要求。我已经扩展了答案。
  • @Angew sizeof 仅告诉原始类型的对齐要求
  • @Angew: sizeof 没有告诉你对齐要求。处理器可以有八字节的double 对象,但只要求它们四字节对齐。 (这可能是因为处理器只有四字节的内存访问,所以它需要使用两次访问来加载/存储一个双精度数,无论它的地址是 0 模 8 还是 4 模 8,所以它不关心哪个确实如此。)对象的对齐要求由_Alignof 运算符报告。
  • @EricPostpischil 很公平,这就是我用“有效”来限定评论的原因。这个问题的表面水平是否能保证如此精细的精确度值得商榷,但我在答案中添加了一个脚注。
【解决方案2】:

你的结构必须是8*N字节长,因为它有一个8字节的成员(double)。这意味着该结构位于内存中可被 8 整除的地址 (A) (A%8 == 0),其结束地址将是 (A + 8N),该地址也可被 8 整除。

从那里,您存储 2 个 4 字节变量 (int + float),这意味着您现在占用了内存区域 [A,A+8)。现在您存储一个 8 字节变量 (double)。自(A+8) % 8 == 0 [自A%8 == 0] 以来,不需要填充。所以,没有填充你会得到4+4+8 == 16

如果您将顺序更改为 int -&gt; double -&gt; float,您将占用 24 个字节,因为 double 变量原始地址不能被 8 整除,并且必须填充 4 个字节才能到达有效地址(以及struct 将在末尾有填充)。


|--------||--------||--------||--------||--------||--------||--------||--------|
|   each ||   cell ||  here  ||represen||-ts  4  || bytes  ||        ||        |
|--------||--------||--------||--------||--------||--------||--------||--------|

A        A+4       A+8      A+12      A+16      A+20      A+24                      [addresses]
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  ||  float || double || double ||        ||        ||        ||        |    [content - basic case]
|--------||--------||--------||--------||--------||--------||--------||--------|

first padding to ensure the double sits on address that is divisble by 8
last  padding to ensure the struct size is divisble by the largest member's size (8)
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  || padding|| double || double || float  || padding||        ||        |    [content - change order case]
|--------||--------||--------||--------||--------||--------||--------||--------|

【讨论】:

  • 首先,刚刚看完你的回答——学到了很多,谢谢!但是,我还有一个问题:在“基本情况”中——为什么我们不需要浮点元素从可被结构的最大元素 (8) 整除的地址开始?
  • 基元需要对齐到它们的大小,而结构需要对齐到它们最大成员的大小(而不是它们自己的大小,即在基本情况下 - 对齐到 8 而不是 16)
  • 你能解释更多关于结构与元素对齐的信息吗?
  • 让我澄清一下:如果对齐用于性能/简单性 - 为什么我们允许将元素放在非 8 因子位置? (就像基本情况下的浮动元素)
  • 1) 性能/简单性需要权衡内存使用。为简单起见,我们只能允许浮点数对齐 8 字节,但这会花费更多。 2)至于为什么-不确定。可以推测这被认为是在内存中打包命中的正确方法 - 您将其切成块以获得最大类型(8 字节),并且您尽可能填充每个块(2 4 字节 / 1 4- byte + 4-padding 等等...)
【解决方案3】:

编译器将插入填充以保证每个元素的偏移量是其大小的某个倍数。

在这种情况下,int 的偏移量=0(相对于结构实例的地址),float 的偏移量=4,double 的偏移量=8,因为int 和@ 的大小987654325@加起来8个。

最后没有填充 - 结构的大小已经是 16,是 double 大小的倍数。

【讨论】:

  • 处理器并不总是要求对象的地址是其大小的倍数。它似乎在 OP 询问的特定情况下成立,但处理器可能有八字节 double 对象,只需要四字节对齐,或者可能有十字节对象,需要 16 字节对齐。
  • @EricPostpischil:当然。还有另一种可能性:不需要对齐,但首选对齐访问。例如,假设 OP 的目标是 x86/64,当地址未对齐时,用于加载 doubleMOVSD 会更昂贵 - 但仍然可能。
【解决方案4】:

首先,C 不会对结构内的成员强制进行任何类型的对齐。这就是为什么很难说 sizeof() 为某个结构的实例返回什么值。

C 对结构成员施加的唯一限制是为第一个成员分配的地址是其对齐的倍数(这样该结构的向量就可以作为指针+sizeof()*index 访问) )。在您的情况下,IntFloatAndDoubleStruct 必须以 sizeof(int) 的倍数对齐,因为 a 的类型为 int

在某些情况下,内部成员分配的地址是其对齐的倍数,以便更快地工作(因此插入了一些填充),但这不是由 C 强加的。因此编译器可能决定插入 #pragma 以更改算法用于分配结构。见here

正如CIsForCookies 建议的那样,该结构将与最大对齐的倍数对齐,以便此类结构的向量仍然使每个成员对齐。但这不是 C 语言强加的,而是考虑到实现的性能,因为在 addr_struct[i]+member_offset 中,这两个术语都可以通过对齐方式整除。

【讨论】:

    猜你喜欢
    • 2015-08-19
    • 2018-09-25
    • 1970-01-01
    • 2010-09-26
    • 2013-07-24
    • 2014-02-19
    • 2014-05-01
    • 2012-05-02
    相关资源
    最近更新 更多