【问题标题】:Question regarding disposition of C-struct members in memory关于在内存中配置 C-struct 成员的问题
【发布时间】:2020-06-08 14:38:32
【问题描述】:

我的问题是基于本页介绍的第三种情况:

https://www.geeksforgeeks.org/is-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member/

// C program to illustrate 
// size of struct 
#include <stdio.h> 

int main() 
{ 

    struct C { 
        // sizeof(double) = 8 
        double z; 

        // sizeof(short int) = 2 
        short int y; 
        // Padding of 2 bytes 

        // sizeof(int) = 4 
        int x; 
    }; 

    printf("Size of struct: %ld", sizeof(struct C)); 

    return 0; 
} 

为什么它需要在 y 之后进行填充,而不是在末尾(x 之后)进行填充?

我可以看到为什么在第 1 和第 2 案例中需要它,但在第 3 号我看不到它。

【问题讨论】:

  • 请将相关代码直接包含在问题中。
  • 最后不需要填充;您需要在未对齐的成员之前进行填充以正确对齐它。因此,如果int 需要或应该与 4 字节边界对齐,则使用填充将其推送到 4 字节边界。
  • 链接页面上的解释有哪些不清楚的地方?
  • @ikegami 4 字节的东西在 32 位或 64 位架构上会有所不同吗?
  • 所需/使用的填充可能因编译器和目标系统而异。但我相信,对于大小为 N 的东西,位于可被 N 整除的地址通常就足够了,因此 32 位与 64 位通常无关紧要。

标签: c


【解决方案1】:

在某些计算机体系结构中,由于对齐限制,访问内存中值的指令将只接受所有地址的子集。例如,一条将 32 位值从内存复制到寄存器的指令可能要求该值位于可被 4 整除的地址。(您可能仍然能够逐字节获取该值,但这会慢得多,因为它需要多条指令)。如果值正确对齐,其他架构可能只会表现更好。而在其他架构中,它可能根本不重要。

因此,C 标准允许在结构中使用特定于实现的填充。通过添加填充,编译器可以确保每个成员都正确对齐(因为它可以强制结构本身对齐)。这允许我们声明以下内容并让编译器计算出确切的大小和偏移量:

struct A { 
    int x; 
    short y; 
    double z;
};

让我们看看编译器可能做什么。

假设您的系统为 short 值使用 2 个字节,int 值使用 4 个字节,double 值使用 8 个字节。假设大小为 N 的值需要放置在可被 N 整除的地址。

struct A { 
    int x;      // 4 bytes, address must be divisible by 4.
    double z;   // 8 bytes, address must be divisible by 8.
    short y;    // 2 bytes, address must be divisible by 2.
};

如果我们只是将成员首尾相连,z 将在偏移量 4 处找到,该偏移量不能被 8 整除,因此计算机将无法有效访问该字段。因此编译器可能会使用填充。

struct A { 
    int x;      // 4 bytes, address must be divisible by 4.   // At offset  0.
                // 4 bytes of padding.                        // At offset  4.
    double z;   // 8 bytes, address must be divisible by 8.   // At offset  8.
    short y;    // 2 bytes, address must be divisible by 2.   // At offset 16.
};

现在,z 位于偏移量 8 处,可被 8 整除。


但这还不够。

对齐限制施加在成员的绝对地址上,而不仅仅是它们的偏移量。因此,struct C 的成员只有在结构本身的地址位于可被 8 整除的地址处时才会正确对齐。编译器可以在您这样做时进行处理

struct A a;

如果你这样做了怎么办

struct A *array = malloc(sizeof(struct A) * n);

malloc 将返回一个满足所有可能对齐限制的指针,因此array[0] 将正确对齐,但是array[1] 呢?为了正确对齐,sizeof(struct A) 需要是 8 的倍数!所以会在末尾添加 padding 使结构体的大小成为 8 的倍数,我们最终得到这样的结果:

// Address must be divisible by 8, so sizeof(struct A) must be divisible by 8.
struct A { 
    int x;      // 4 bytes, address must be divisible by 4.   // At offset  0.
                // 4 bytes of padding.                        // At offset  4.
    double z;   // 8 bytes, address must be divisible by 8.   // At offset  8.
    short y;    // 2 bytes, address must be divisible by 2.   // At offset 16.
                // 2 bytes of padding.                        // At offset 18.
};

最后,您询问了struct C。应用上述,​​我们得到:

// Address must be divisible by 8, so sizeof(struct C) must be divisible by 8.
struct C { 
    double z;   // 8 bytes, address must be divisible by 8.   // At offset  0.
    short y;    // 2 bytes, address must be divisible by 2.   // At offset  8.
                // 2 bytes of padding.                        // At offset 10.
    int x;      // 4 bytes, address must be divisible by 4.   // At offset 12.
                // 0 bytes of padding.                        // At offset 16.
};

【讨论】:

  • 非常感谢。这正是我所要求的!
  • @ikegami 感谢更新。我已经阅读了所有内容,现在更加清楚了。 :)
【解决方案2】:

如网站所述,

"C语言不允许编译器对struct成员重新排序以减少padding的量。为了尽量减少padding的量,struct成员必须按降序排序(类似于案例2) 。”

这意味着结构的填充是在它们创建后立即创建的。 C语言不能对struct成员重新排序,所以代码运行如下:先为double z创建8个字节的存储,然后为short int y创建2个字节的存储,填充2个字节,最后创建4个字节的存储对于整数 x。您应该将填充视为与原始存储的一个包:您无法将它们分开,这就是为什么在 x 的存储之前创建 y 的填充。

编辑:对不起,如果我的回答有点混乱或没有回答问题。 x 是一个 int,所以 x 没有任何填充。 y 是一个 short int,所以它有 2 个字节的填充。最后没有填充,因为它带有需要它的变量(y)。

-mihirm

【讨论】:

  • 感谢您的回答,但您只是复制/粘贴了页面上的内容。正如我所说,我的问题是为什么必须在“X”之前填充而不是在“X”之后留下空格。
猜你喜欢
  • 1970-01-01
  • 2012-03-08
  • 2021-08-27
  • 2021-06-30
  • 2020-04-02
  • 2010-12-10
  • 2020-04-01
  • 1970-01-01
  • 2021-11-27
相关资源
最近更新 更多