我有 C# 背景。我对 C 这样的低级语言来说是个新手。
在C#中,
struct
的内存是由编译器默认布局的。编译器可以重新排序数据字段或隐式在字段之间填充附加位。因此,我必须指定一些特殊属性来覆盖此行为以获得精确的布局。
AFAIK,默认情况下,C 不会重新排序或对齐
struct
的内存布局。不过,我听说有一个很难找到的例外。
C 的内存布局行为是什么?什么应该重新排序/对齐,什么不应该?
它是特定于实现的,但实际上规则(在没有
#pragma pack
等的情况下)是:
sizeof(T)
字节对齐。因此,给出以下结构:
struct ST
{
char ch1;
short s;
char ch2;
long long ll;
int i;
};
ch1
位于偏移 0s
偏移量 2ch2
位于偏移量 4 处,紧接在 sll
偏移量 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
};
在 C 中,编译器可以为每个基本类型指定某种对齐方式。通常,对齐方式是类型的大小。但这完全是特定于实现的。
引入填充字节,以便每个对象都正确对齐。不允许重新订购。
可能每个远程现代编译器都实现
#pragma pack
,它允许控制填充并将其留给程序员以遵守 ABI。 (不过,这完全是非标准的。)
来自 C99 §6.7.2.1:
12 一个的每个非位域成员 结构或联合对象已对齐 以实现定义的方式 适合其类型。
13 在一个 结构体对象,非位域 会员及所在单位 位字段驻留的地址 按它们的顺序增加 被宣布。指向结构体的指针 对象,经过适当转换,指向 它的初始成员(或者如果该成员 是一个位域,那么到单位 它所在的位置),反之亦然。 内可能有未命名的填充 结构对象,但不是它的 开始。
您可以首先阅读数据结构对齐维基百科文章,以更好地了解数据对齐。
摘自维基百科文章:
数据对齐意味着将数据放置在等于字大小的某个倍数的内存偏移处,这会由于CPU处理内存的方式而提高系统的性能。为了对齐数据,可能需要在最后一个数据结构的末尾和下一个数据结构的开头之间插入一些无意义的字节,这就是数据结构填充。
来自 GCC 文档的 6.54.8 Structure-Packing Pragmas:
为了与微软兼容 Windows编译器,GCC支持一套 #pragma 指令改变了 成员的最大对齐 结构(零宽度除外) 位域)、联合和类 随后定义。 n值 下面总是需要是一个小 2 的幂并指定新的 以字节为单位对齐。
只需设置新的对齐方式。#pragma pack(n)
将对齐方式设置为之前的对齐方式 编译开始时的效果(参见 还有命令行选项 -fpack-struct[=] 请参阅代码生成选项)。#pragma pack()
将当前对齐设置推送到 内部堆栈,然后可选 设置新的对齐方式。#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