我有C#编程背景。像C这样的低级语言对我来说还是新手。
在C#中,struct
的内存布局默认由编译器进行排列。编译器可以隐式地重新排序数据字段或在字段之间填充额外的位。因此,我必须指定一些特殊的属性来覆盖此行为以获得精确的布局。
据我所知,C不会默认重新排序或对齐struct
的内存布局。但是,我听说有一个很难找到的小例外。
C的内存布局行为是什么?应该重新排序/对齐什么和不应该?
我有C#编程背景。像C这样的低级语言对我来说还是新手。
在C#中,struct
的内存布局默认由编译器进行排列。编译器可以隐式地重新排序数据字段或在字段之间填充额外的位。因此,我必须指定一些特殊的属性来覆盖此行为以获得精确的布局。
据我所知,C不会默认重新排序或对齐struct
的内存布局。但是,我听说有一个很难找到的小例外。
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之后因此,sizeof(ST)
为24。
通过重新排列成员以避免填充,可以将其减小到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 在结构体对象内,非位域成员和位域所驻留的单元的地址按照它们声明的顺序增加。指向结构体对象的指针经过适当转换后,指向它的初始成员(如果该成员是位域,则指向所驻留的单元),反之亦然。结构体对象内可能存在未命名的填充,但不能在其开始处出现。
#pragma pack
相同的效果,但对语义具有更细粒度的控制。 - Chris Lutz_Alignas
。 - idmean来自GCC文档6.54.8结构体打包指令:数据对齐是指将数据放置在内存偏移量等于某个字大小的倍数上,这会增加系统的性能,因为CPU处理内存的方式。为了对齐数据,可能需要在上一个数据结构的末尾和下一个数据结构的开始之间插入一些无意义的字节,这就是数据结构填充。
#pragma pack(n)
仅设置新的对齐方式。#pragma pack()
将对齐方式设置为编译开始时有效的对齐方式(也可以使用命令行选项 -fpack-struct[=] 参见 Code Gen Options)。#pragma pack(push[,n])
将当前对齐设置推送到内部堆栈上,然后可选择设置新的对齐方式。#pragma pack(pop)
将对齐设置恢复为在内部堆栈顶部保存的设置(并删除该堆栈条目)。请注意,#pragma pack([n])
不影响此内部堆栈;因此可以有 #pragma pack(push)
后跟多个 #pragma pack(n)
实例,最后由一个 #pragma pack(pop)
完成。#pragma
,它将结构体布局为文档中记录的 __attribute__ ((ms_struct))
。
#pragma ms_struct on
打开声明的结构体的布局。#pragma ms_struct off
关闭声明的结构体的布局。#pragma ms_struct reset
返回默认布局。
char
成员。 - Deduplicatorsizeof(T)
字节。例如,在常见的32位架构上,double
是8字节,但通常只需要4字节对齐。此外,结构体末尾的填充仅填充到最宽的结构体成员的对齐方式。例如,一个由3个char变量组成的结构体可能没有填充。 - Matt