C中的结构内存布局
我有一个C#背景。 我非常喜欢像C这样的低级语言。
在C#中, struct
的内存由编译器默认布局。 编译器可以隐式地重新排序数据字段或填充字段之间的附加位。 因此,我必须指定一些特殊属性来覆盖此行为以获得精确布局。
AFAIK,C默认情况下不重新排序或对齐struct
的内存布局。 但是,我听说有一个很难找到的例外。
什么是C的内存布局行为? 什么应该重新排序/对齐而不是?
在C中,允许编译器为每种基本类型指定一些对齐。 通常,对齐是类型的大小。 但它完全是针对具体实施的。
引入填充字节,以便每个对象都正确对齐。 不允许重新排序。
可能每个远程现代编译器都实现了#pragma pack
,它允许控制填充并将其留给程序员以符合ABI。 (但它严格来说是非标准的。)
从C99§6.7.2.1开始:
12结构或联合对象的每个非位字段成员都以适合其类型的实现定义方式对齐。
13在结构对象中,非位字段成员和位字段所在的单元具有按声明顺序增加的地址。 指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 结构对象中可能存在未命名的填充,但不是在其开头。
它是特定于实现的,但实际上规则(在没有#pragma pack
之类的情况下)是:
- Struct成员按其声明的顺序存储。 (这是C99标准所要求的,如前所述。)
- 如有必要,在每个结构成员之前添加填充,以确保正确对齐。
- 每个基元类型T需要对齐
sizeof(T)
字节的sizeof(T)
。
所以,给定以下结构:
struct ST { char ch1; short s; char ch2; long long ll; int i; };
-
ch1
位于偏移0处 - 插入填充字节以对齐…
- 在偏移2处
- 在s之后,
ch2
位于偏移4处 - 插入3个填充字节以对齐…
- 在偏移8处
- 在ll之后,
i
在偏移16处 - 最后添加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;
您可以从阅读数据结构对齐维基百科文章开始,以更好地理解数据对齐。
来自维基百科的文章 :
数据对齐意味着将数据放入存储器偏移量等于字大小的某个倍数,这会因CPU处理内存的方式而提高系统性能。 为了对齐数据,可能需要在最后一个数据结构的末尾和下一个数据结构的开始之间插入一些无意义的字节,即数据结构填充。
从6.54.8 GCC文档的结构包装编译 :
为了与Microsoft Windows编译器兼容,GCC支持一组#pragma指令,这些指令可以更改随后定义的结构(除了零宽度位域除外),联合和类的成员的最大对齐。 下面的n值始终需要是2的小幂,并以字节为单位指定新的对齐。
pragma pack(n)只是设置新的对齐方式。
pragma pack()将对齐设置为其中的对齐方式
编译开始时的效果(另请参阅命令行选项-fpack-struct [=]请参阅代码选项选项)。
pragma pack(push [,n])按下当前对齐设置
内部堆栈然后可选择设置新的对齐方式。
pragma pack(pop)将对齐设置恢复为保存的对齐设置
内部堆栈的顶部(并删除该堆栈条目)。 请注意,
enter code here
#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返回默认布局。