我有 C# 背景。我是 C 等低级语言的新手。
在 C# 中,struct
默认情况下, 的内存由编译器布局。编译器可以重新排序数据字段或隐式填充字段之间的附加位。因此,我必须指定一些特殊属性来覆盖此行为以实现精确布局。
struct
AFAIK,默认情况下,C 不会重新排序或对齐 a 的内存布局。但是,我听说有一个很难找到的小例外。
C 的内存布局行为是什么?什么应该重新排序/对齐而不是?
我有 C# 背景。我是 C 等低级语言的新手。
在 C# 中,struct
默认情况下, 的内存由编译器布局。编译器可以重新排序数据字段或隐式填充字段之间的附加位。因此,我必须指定一些特殊属性来覆盖此行为以实现精确布局。
struct
AFAIK,默认情况下,C 不会重新排序或对齐 a 的内存布局。但是,我听说有一个很难找到的小例外。
C 的内存布局行为是什么?什么应该重新排序/对齐而不是?
它是特定于实现的,但实际上规则(在没有#pragma pack
或类似的情况下)是:
sizeof(T)
字节对齐。因此,给定以下结构:
struct ST
{
char ch1;
short s;
char ch2;
long long ll;
int i;
};
ch1
位于偏移量 0s
在偏移 2ch2
在偏移量 4 处,紧接在 s 之后ll
在偏移 8i
在偏移量 16 处,就在 ll 之后24也是如此sizeof(ST)
。
通过重新排列成员以避免填充,可以将其减少到 16 个字节:
struct ST
{
long long ll; // @ 0
int i; // @ 8
short s; // @ 12
char ch1; // @ 14
char ch2; // @ 15
} ST;
在 C 语言中,允许编译器为每种基本类型规定某种对齐方式。通常对齐是类型的大小。但它完全是特定于实现的。
引入了填充字节,因此每个对象都正确对齐。不允许重新排序。
可能每个远程现代编译器都实现#pragma pack
了允许控制填充并将其留给程序员以遵守 ABI。(不过,这完全是非标准的。)
从 C99 §6.7.2.1 开始:
12 结构或联合对象的每个非位域成员都以适合其类型的实现定义的方式对齐。
13 在结构对象中,非位域成员和位域所在的单元的地址按声明顺序递增。一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。结构对象中可能有未命名的填充,但不是在其开头。
您可以从阅读数据结构对齐维基百科文章开始,以更好地理解数据对齐。
来自维基百科文章:
数据对齐意味着将数据放置在等于字大小的某个倍数的内存偏移处,由于 CPU 处理内存的方式,这会提高系统的性能。为了对齐数据,可能需要在最后一个数据结构的结尾和下一个数据结构的开头之间插入一些无意义的字节,这就是数据结构填充。
从GCC 文档的6.54.8 Structure-Packing Pragmas开始:
为了与 Microsoft 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
回到默认布局。