为什么无论成员的顺序如何,这种结构的大小都是相同的?

Why the size of this structure is same irrespective of the order of the members?

提问人:Neeraj-Kumar-Coder 提问时间:1/30/2021 最后编辑:brucegNeeraj-Kumar-Coder 更新时间:2/2/2021 访问量:58

问:

由于 C 中结构的内存对齐是以连续的形式完成的,首先是第一个元素,然后是第二个元素,然后是第三个元素,依此类推......除了位填充,那么为什么即使重新排列元素,这个结构的大小也是一样的:

#include <stdio.h>

int main(void)
{
    struct student
    {
        float c;
        int a;
        char b;
    };
    printf("%zu\n", sizeof(struct student));
    return 0;
}

输出:12

对于上述结构配置,内存对齐是否如下所示?

f f f f i i i i c
_ _ _ _ _ _ _ _ _ _ _
0 1 2 3 4 5 6 7 8 9 10
c 对准 结构 尺寸

评论

1赞 dbush 1/30/2021
为什么你认为尺寸不一样?您希望哪种字段的顺序具有不同的大小?
0赞 Neeraj-Kumar-Coder 1/30/2021
通过重新排列成员,内存分配的顺序会发生变化。也就是说,在这种情况下,第一个元素会有所不同,在这种结构的情况下,As 所占用的内存也会有所不同: struct student { int b; char a; char c; };structure

答:

1赞 Adrian Mole 1/30/2021 #1

在系统上,和 数据类型的大小似乎为 4 个字节;因此,编译器(除非另有说明)将在 4 字节边界上对齐这些类型的结构成员。这就是为什么,如果您将该字段作为第二个成员,则将在该字段和下一个字段之间添加 3 个字节的“填充”,从而为结构提供 12 个字节的总大小。intfloatchar b

但是,编译器也会在结构的末尾添加填充!(同样,在最后一个字段是 when 的情况下,三个字节。char b

为什么?好吧,考虑一个这样的类型的数组。如果没有这种“终端填充”,第二个数组元素的第一个字段将错位——也就是说,它不会在 4 字节边界上,从而降低从“内部”填充中获得的任何效率。对于将类型作为嵌套字段包含的其他结构,也会出现类似的问题。struct

编辑:我无法真正对此维基百科页面上的以下声明提供太多改进:

需要注意的是,最后一个成员用数字填充 所需的字节数,因此结构的总大小应为 任何结构构件的最大对齐的倍数......

评论

0赞 Neeraj-Kumar-Coder 1/30/2021
填充是如何完成的:尺寸显示是structstruct sample { char b; int a; char c; };12
0赞 Adrian Mole 1/30/2021
@Neeraj-Kumar-Coder 同样,非常相似:3 个字节后和 3 个字节后。bc
0赞 Adrian Mole 1/30/2021
@Neeraj-Kumar-Coder 但这个会有所不同:......之后只有 2 个字节的填充,总大小为 8 个字节。struct sample { char b; char c; int a; };c
0赞 Neeraj-Kumar-Coder 1/30/2021
这意味着如果在最后一个成员中,则只完成额外的填充。同样在我的评论示例中,如果跳过额外的填充,第一个成员不会错位。char
2赞 Myst 1/30/2021 #2

似乎在您的机器上,每个都需要 4 个字节。从这个意义上说,它们在中的位置是无关紧要的。intfloatstruct

但是,a (通常)只需要 1 个字节,所以您可能想知道为什么 come 不返回 (4+4+1),原因是填充charsizeof9

在许多情况下都会添加填充,但最明显的是允许类型对齐(我假设系统上的 and 类型在 4 字节边界上自然对齐)。intfloat

我认为如果将顺序更改为:

struct student
{
    float c;
    char b;
    int a;
};

在此示例中,我们将有 4 个字节 () + 1 个字节 () + 3 个字节(填充)+ 4 个字节 ()。即:floatcharint

struct student
{
    float c;
    char b;
    char padding[3];
    int a;
};

但是,在原始示例中,我们有:

struct student
{
    float c;
    int a;
    char b;
};

这会产生 4 个字节 () + 4 个字节 () + 1 个字节 () + 3 个字节(填充) - 即:floatintchar

struct student
{
    float c;
    int a;
    char b;
    char padding[3];
};

我们仍然在末尾获得填充的原因是允许数组 ()。structstruct student array[32]

如果末尾没有任何填充,则数组 () 的第二个成员将从偏移量开始,并且类型 () 将无法在自然的 4 字节边界上正确对齐。structarray[1]float

声明类型时,编译器将始终添加允许在数组中使用该类型的所需填充(即,在使用 .malloc

我希望这能回答你的问题。


编辑

为了澄清我上面没有列出的其余部分的填充(参见注释),它可能看起来像这样(假设编译器正在为类似的系统编译代码):struct

struct student
{
    char b;
    char padding[3];
    float c;
    int a;
};

如果也是,我们将在以下两端获得填充:acharstruct

struct student
{
    char b;
    char padding[3];
    float c;
    char a;
    char padding2[3];
};

但是,如果我们重新组织 字符彼此相邻,它们的填充会有所不同,因为该类型在此系统上没有 4 个字节的自然对齐:structchar

struct student
{
    char b;
    char a;
    char padding[2];
    float c;
};

注意

大多数编译器应该支持一个指令,告诉编译器“打包”结构(忽略类型对齐和填充)......但是,恕我直言,应该强烈建议不要这样做,因为它可能会导致某些 CPU 架构崩溃并引入不可移植的代码(另请参阅此处)。

评论

0赞 Neeraj-Kumar-Coder 1/30/2021
你能告诉我填充是如何在这种结构中完成的吗? 显示尺寸是struct student { char b; int a; char c; };12
0赞 Myst 1/30/2021
@Neeraj-Kumar-Coder - 我编辑了答案,并回应了您的评论。
0赞 Luis Colorado 2/2/2021 #3

编译器对齐字段并填充 的其余部分,因此,如果您构建一个数组,下一个元素将对齐。struct

在您发布的案例中,字段元素的对齐方式是两个字段的大小,大小为 4 个字节。因此,编译器在末尾填充结构,以便您定义的类型的下一个数组元素也对齐。这意味着编译器必须在类型化字段之后添加三个 pad 元素,即使它位于结构的末尾。floatintchar

在你发布的情况下,如果你认为结构是 5 个字节,结构数组的下一个元素将不会对齐,因为浮点数和 int 将从对齐的偏移量开始。+1