C中的结构内存布局

Posted

技术标签:

【中文标题】C中的结构内存布局【英文标题】:Struct memory layout in C 【发布时间】:2011-02-14 11:51:22 【问题描述】:

我有 C# 背景。我是 C 等低级语言的新手。

在 C# 中,struct 的内存默认由编译器布局。编译器可以重新排序数据字段或隐式填充字段之间的附加位。因此,我必须指定一些特殊属性来覆盖此行为以实现精确布局。

AFAIK,默认情况下,C 不会重新排序或对齐 struct 的内存布局。但是,我听说有一个很难找到的小例外。

什么是 C 的内存布局行为?什么应该重新排序/对齐而不是?

【问题讨论】:

【参考方案1】:

您可以从阅读data structure alignment wikipedia article 开始,以更好地了解数据对齐。

来自wikipedia article:

数据对齐意味着将数据放置在等于字大小的某个倍数的内存偏移处,由于 CPU 处理内存的方式,这提高了系统的性能。为了对齐数据,可能需要在最后一个数据结构的结尾和下一个数据结构的开头之间插入一些无意义的字节,这就是数据结构填充。

来自 GCC 文档的6.54.8 Structure-Packing Pragmas:

为了与微软兼容 Windows 编译器,GCC 支持一套 的 #pragma 指令改变了 成员的最大对齐 结构(除了零宽度 位域)、联合和类 随后定义。 n 值 下面总是需要一个小的 2 的幂并指定新的 以字节为单位对齐。

    #pragma pack(n) 只是设置新的对齐方式。 #pragma pack() 将对齐设置为 编译开始时的效果(见 还有命令行选项 -fpack-struct[=] 参见代码生成选项)。 #pragma pack(push[,n]) 将当前对齐设置推送到 内部堆栈,然后可选 设置新的对齐方式。 #pragma pack(pop) 将对齐设置恢复为保存在 内部堆栈的顶部(和 删除该堆栈条目)。注意 #pragma pack([n]) 不影响这个内部堆栈;因此它是 可能有#pragma pack(push) 后跟多个#pragma pack(n) 实例并由单个完成 #pragma pack(pop)

一些目标,例如i386和powerpc, 支持 ms_struct #pragma 布置一个结构作为记录 __attribute__ ((ms_struct)).

    #pragma ms_struct on 打开声明结构的布局。 #pragma ms_struct off 关闭声明结构的布局。 #pragma ms_struct reset 回到默认布局。

【讨论】:

感谢您的关心。我按照您的指导修改了问题。【参考方案2】:

在 C 语言中,允许编译器为每个原始类型指定某种对齐方式。通常对齐是类型的大小。但它完全是特定于实现的。

引入了填充字节,因此每个对象都正确对齐。不允许重新排序。

可能每个远程现代编译器都实现了#pragma pack,它允许控制填充并将其留给程序员以遵守ABI。 (不过,这完全是非标准的。)

来自 C99 §6.7.2.1:

12 每个非位域成员 结构或联合对象对齐 以实现定义的方式 适合其类型。

13 在一个 结构对象,非位域 成员及所在单位 位域驻留的地址 按它们的顺序增加 被宣布。指向结构的指针 适当转换的对象指向 它的初始成员(或者如果该成员 是一个位域,然后到单位 它所在的位置),反之亦然。 内可能有未命名的填充 结构对象,但不在其 开始。

【讨论】:

一些编译器(即 GCC)实现了与 #pragma pack 相同的效果,但对语义进行了更细粒度的控制。 C11 也有_Alignas【参考方案3】:

它是特定于实现的,但实际上规则(在没有#pragma pack 或类似的情况下)是:

结构成员按照声明的顺序存储。 (如前所述,这是 C99 标准所要求的。) 如有必要,在每个结构成员之前添加填充,以确保正确对齐。 每个基本类型 T 都需要 sizeof(T) 字节对齐。

所以,给定以下结构:

struct ST

   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
;
ch1 位于偏移量 0 插入一个填充字节以对齐... s 在偏移 2 处 ch2 位于偏移量 4 处,紧跟在 s 之后 插入 3 个填充字节以对齐... ll 在偏移 8 处 i 在偏移量 16 处,就在 ll 之后 最后添加了 4 个填充字节,因此整个结构是 8 个字节的倍数。我在 64 位系统上检查了这一点:32 位系统可能允许结构具有 4 字节对齐。

所以sizeof(ST) 是 24。

可以通过重新排列成员来减少到 16 个字节以避免填充:

struct ST

   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
 ST;

【讨论】:

如有必要,在之前添加填充......更像是在之后。最好在您的示例中添加最后一个 char 成员。 原始类型不一定需要sizeof(T) 字节对齐。例如,常见的 32 位架构上的 double 是 8 个字节,但 often only requires 4-byte alignment。此外,结构末尾的填充仅填充最宽结构成员的对齐方式。例如,一个 3 个 char 变量的结构可以没有填充。 @dan04,按 sizeof(T) 的降序排列结构是否是一个好习惯。这样做有什么缺点吗?

以上是关于C中的结构内存布局的主要内容,如果未能解决你的问题,请参考以下文章

具有相同成员类型的 C 结构是不是保证在内存中具有相同的布局?

浅析C++内存布局

浅析内存对齐与ANSI C中struct型数据的内存布局

结构体对齐——结构体内存布局

图文详解Java对象内存布局

c执行文件内存布局