又错了,字节对齐及#pragma pack的使用

💡 原文中文,约7400字,阅读约需18分钟。
📝

内容提要

C编译器默认按自然对齐分配结构体空间:成员按自身类型长度对齐,必要时插入填充字节;结构体整体按最大成员对齐,末尾补齐。可用#pragma pack(n)指定对齐,成员和整体均取n与自身长度的较小值;当n不小于所有成员长度时该指令无效。还可用__attribute__((aligned(n)))或packed控制对齐。文中以多个结构体示例说明不同声明顺序及pack(2)下的sizeof结果。

🔎

延伸解读

成员顺序如何影响结构体大小

文章通过四个结构体示例说明:即使成员类型相同,声明顺序不同也会导致sizeof结果不同。默认对齐下,前三个示例均为12字节,而将char、char、short、int按长度递增排列的第四个示例仅为8字节。这是因为编译器会在成员间插入填充字节以满足对齐要求,合理调整顺序可减少填充,但文章未给出通用排序规则。

#pragma pack(n)的实际效果与边界

文章指出,#pragma pack(n)会同时影响成员对齐和结构体整体对齐,规则是取n与成员自身长度(或最大成员长度)中的较小值。因此当n不小于所有成员长度时,该指令不产生效果。示例中pack(2)使部分结构体从12字节降为10或8字节,但并非所有结构体都缩小,说明其效果取决于成员类型和顺序。

嵌套结构体时的对齐传递

文章特别提醒,当结构体包含子结构体时,子结构成员的对齐取#pragma pack指定值与子结构最大成员长度中的较小值。示例中pack(8)下s2的sizeof为24,并给出了s1和s2的内存布局。这表明嵌套场景下对齐规则会逐层作用,不能简单按外层最大成员推断,需要结合子结构内部布局分析。

两种控制对齐的替代方式

除#pragma pack外,文章还介绍了__attribute__((aligned(n)))和__attribute__((packed))。前者让成员对齐到n字节自然边界,若成员长度大于n则按最大成员长度对齐;后者取消优化对齐,按实际占用字节数对齐。文章提到n可取1、2、4、8、16等,并称第一种方式较为常见,但未展开比较两者差异或适用场景。

❓

Q&A

C编译器默认的结构体字节对齐规则是什么?

C编译器默认采用自然对齐:每个成员按其数据类型的长度对齐(如int按4字节,double按8字节),若起始偏移不满足则在前一成员后填充空字节;结构体整体按所有成员中最大的对齐条件对齐,若总长度不是该条件的整数倍,则在末尾填充空字节。

为什么结构体成员声明顺序会影响sizeof的大小?

因为编译器按成员声明顺序分配空间,并在成员之间插入填充字节以满足各自的对齐要求。不同的顺序会导致不同的填充量,从而影响结构体总大小。例如,将char、char、short、int依次排列可能比char、int、short、char更紧凑。

#pragma pack(n) 的对齐规则具体是怎样的?

使用#pragma pack(n)后,每个数据成员的对齐取n和该成员自身长度中较小的值;结构体整体对齐取n和最大成员长度中较小的值。当n大于或等于所有成员长度时,该指令无效。

如何取消#pragma pack设置的自定义对齐?

使用#pragma pack()可以取消自定义字节对齐方式,恢复编译器默认的自然对齐。

结构体中包含子结构体时,使用#pragma pack如何对齐?

子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中较小的那个进行对齐。例如,在#pragma pack(8)下,若子结构最大成员为long(假设4字节),则子结构成员按4字节对齐。

__attribute__((aligned(n))) 和 __attribute__((packed)) 分别有什么作用?

__attribute__((aligned(n)))让结构成员对齐在n字节自然边界上,如果成员长度大于n,则按最大成员长度对齐;__attribute__((packed))取消编译过程中的优化对齐,按实际占用字节数对齐,即紧凑排列。

🏷️

标签

➡️

继续阅读