优秀文章参考:
#pragma pack(push) 和#pragma pack(pop) 以及#pragma pack() - Crystal_Guang - 博客园
c语言内存对齐与#pragma pack(n) - long_ago - 博客园
目录
问题引入
一、#pragma pack(push,n)、#pragma pack(pop)和#pragma pack(n)1.区别与联系
2.字节对齐概念二、功能与使用
1.#pragma pack(push,n)
2.#pragma pack(pop)
3.#pragma pack(n)
4.实例:嵌套使用对齐方式三、使用建议
四、注意事项
问题引入
结构体内存对齐字节可以通过
#pragma pack(n)
的方式来指定。但是,有没有想过一个问题,某些时候想4字节对齐,有些时候我又想1字节或者8字节对齐,那么怎么解决这个问题呢?
此时,
#pragma pack(push)
和#pragma pack(pop)
以及#pragma pack()
应运而生。
一、#pragma pack(push, n)
、#pragma pack(pop)
和 #pragma pack(n)
用于控制结构体、联合体或类的数据对齐方式的编译器指令。
1. 区别与联系
特性 | #pragma pack(push, n) | #pragma pack(pop) | #pragma pack(n) |
---|---|---|---|
对齐方式修改 | 临时修改为 n 字节 |
恢复到之前保存的对齐方式 | 修改全局对齐方式为 n 字节 |
对全局默认对齐影响 | 不改变全局默认对齐 | 不改变全局默认对齐 | 改变全局默认对齐 |
对齐方式恢复 | 保存当前对齐方式,便于恢复 | 恢复最近的 push 设置 |
无恢复机制 |
推荐使用场景 | 临时修改,代码块对齐 | 恢复对齐设置,避免全局影响 | 全局调整对齐方式 |
嵌套支持 | 支持嵌套 | 支持嵌套 | 不支持嵌套 |
2. 字节对齐概念
单字节对齐
指数据在内存中的排列方式,它确保每个数据成员按照**1字节(8位)**的边界对齐,而不是按照系统默认的对齐方式(通常是4字节或更高)。
在内存中,不同的数据类型通常有不同的对齐规则:
- 默认对齐:系统会按照数据类型的大小对齐,例如:
uint8
按 1 字节对齐。uint16
按 2 字节对齐。uint32
按 4 字节对齐。对齐的目的:提高内存访问效率,但有时会引入填充字节,以满足对齐要求。
二、功能与使用
1. #pragma pack(push, n)
- 功能:将当前的对齐方式保存到一个堆栈中,并设置新的对齐方式为 n(字节对齐);
- 典型用法:用于在需要临时改变对齐规则时保存当前状态,便于后续恢复。
/*
StructA 按 2 字节对齐,节省空间。
StructB 按默认对齐方式(假设为 4 字节)对齐,内存占用更多。
*/
#include <stdio.h>
#pragma pack(push, 2) // 保存当前对齐方式,并设置为 2 字节对齐
typedef struct {
char a; // 1 字节
int b; // 4 字节
} StructA;
#pragma pack(pop) // 恢复之前的对齐方式
typedef struct {
char a; // 默认对齐
int b; // 默认对齐
} StructB;
int main() {
printf("StructA size: %zu bytes\n", sizeof(StructA)); // 紧凑对齐
printf("StructB size: %zu bytes\n", sizeof(StructB)); // 默认对齐
return 0;
}
2.#pragma pack(pop)
- 功能:从堆栈中恢复上一次保存的对齐方式;
- 典型用法:与
#pragma pack(push, n)
配合使用,确保代码在临时修改对齐方式后恢复默认状态,避免影响后续代码。
/*
StructC 紧凑对齐,占用 5 字节。
StructD 恢复默认对齐方式(假设为 4 字节),内存占用可能更大。
*/
#pragma pack(push, 1) // 设置为 1 字节对齐
typedef struct {
char a;
int b;
} StructC;
#pragma pack(pop) // 恢复默认对齐方式
typedef struct {
char a;
int b;
} StructD;
3. #pragma pack(n)
- 功能:直接修改全局对齐方式为
n
字节,无需保存之前的状态。 - 典型用法:用于代码中需要长时间使用特定对齐方式的场景。
#pragma pack(2) // 设置全局对齐方式为 2 字节
typedef struct {
char a;
int b;
} StructE;
// 此处所有代码均受 2 字节对齐方式影响
4. 实例:嵌套使用对齐方式
假设需要定义一组与硬件通信协议相关的结构体,有不同的对齐要求:
- 全局默认对齐为 4 字节。
- 某些结构体需要紧凑对齐(如 1 字节)。
- 在局部范围内调整为特定对齐方式(如 2 字节)。
#include <stdio.h>
// 全局默认对齐为 4 字节
#pragma pack(4)
typedef struct {
char a; // 1 字节
int b; // 4 字节
} GlobalAlignedStruct;
#pragma pack(push, 1) // 保存全局 4 字节对齐,临时调整为 1 字节对齐
typedef struct {
char a; // 1 字节
int b; // 紧凑对齐,无填充,占用 4 字节
short c; // 2 字节
} TightlyPackedStruct;
#pragma pack(push, 2) // 在 1 字节对齐的基础上进一步设置为 2 字节对齐
typedef struct {
char a; // 1 字节
short b; // 2 字节对齐
int c; // 4 字节对齐
} TwoByteAlignedStruct;
#pragma pack(pop) // 恢复到 1 字节对齐
typedef struct {
char a; // 1 字节
int b; // 紧凑对齐,无填充,占用 4 字节
short c; // 2 字节
} RestoredTightlyPackedStruct;
#pragma pack(pop) // 恢复到全局默认 4 字节对齐
typedef struct {
char a; // 1 字节
int b; // 默认对齐,4 字节
short c; // 默认对齐,4 字节
} DefaultAlignedStruct;
int main() {
printf("Size of GlobalAlignedStruct: %zu bytes\n", sizeof(GlobalAlignedStruct));
printf("Size of TightlyPackedStruct: %zu bytes\n", sizeof(TightlyPackedStruct));
printf("Size of TwoByteAlignedStruct: %zu bytes\n", sizeof(TwoByteAlignedStruct));
printf("Size of RestoredTightlyPackedStruct: %zu bytes\n", sizeof(RestoredTightlyPackedStruct));
printf("Size of DefaultAlignedStruct: %zu bytes\n", sizeof(DefaultAlignedStruct));
return 0;
}
解释
- 全局对齐方式:
- 默认对齐为 4 字节,
GlobalAlignedStruct
使用全局对齐方式。
- 1 字节对齐范围:
- 使用
#pragma pack(push, 1)
保存当前对齐方式(4 字节),并临时设置为 1 字节对齐。TightlyPackedStruct
使用 1 字节对齐,所有成员紧凑排列,无填充。
- 嵌套对齐调整:
- 在 1 字节对齐范围内再次使用
#pragma pack(push, 2)
设置为 2 字节对齐。TwoByteAlignedStruct
受 2 字节对齐规则影响,适当填充以满足对齐要求。
- 恢复到外层对齐:
- 使用
#pragma pack(pop)
恢复到 1 字节对齐。RestoredTightlyPackedStruct
使用恢复的 1 字节对齐规则。
- 全局对齐恢复:
- 使用第二个
#pragma pack(pop)
恢复全局 4 字节对齐。DefaultAlignedStruct
使用默认的 4 字节对齐规则。输出示例(可能因编译器不同而略有差异)
Size of GlobalAlignedStruct: 8 bytes Size of TightlyPackedStruct: 7 bytes Size of TwoByteAlignedStruct: 8 bytes Size of RestoredTightlyPackedStruct: 7 bytes Size of DefaultAlignedStruct: 12 bytes
三、使用建议
- 推荐使用
#pragma pack(push, n)
和#pragma pack(pop)
:- 适合局部调整对齐方式,尤其是在多个文件中包含不同的对齐要求时。
- 保持代码清晰,易于维护,避免无意间影响其他代码。
- 谨慎使用
#pragma pack(n)
:- 会修改全局对齐方式,可能导致不易察觉的问题。
- 如果使用,务必在之后通过
#pragma pack()
恢复默认设置。
四、注意事项
- 性能影响:
- 非对齐内存访问可能会导致性能下降(取决于硬件架构)。
- 避免在高性能关键路径中使用严格的对齐方式(如
1 字节对齐
)。
- 跨平台兼容性:
- 不同平台的默认对齐方式可能不同,因此明确设置对齐方式可以提高可移植性。
- 协议解析和硬件交互:
- 通常使用
1 字节对齐
,以确保内存布局与外部设备或协议数据结构严格一致。
- 通常使用