C++ Builder 参考手册 ➙ 对齐方式
有时候,我们发现 C++ 结构体的字节数并不等于这个结构体所有成员字节数的总和,而是比这个字节数总和要多,例如下面的一段程序:
typedef struct
{
char a;
float b;
short c;
double d;
} TMyStruct;
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(sizeof(TMyStruct));
}
运行结果为 24,即这个结构体的大小为 24 个字节:
也就是 TMyStruct 结构体的大小是 24 个字节,而每个成员的总和只有 15 个字节。
char a; // 1个字节
float b; // 4个字节
short c; // 2个字节
double d; // 8个字节
------→ 一共15个字节
如果希望这个结构体的字节数和这个结构体所有成员的字节数总和相等,需要在这个结构体放在 #pragma pack(push,1) 和 #pragma pack(pop) 之间:
#pragma pack(push,1)
typedef struct
{
char a;
float b;
short c;
double d;
} TMyStruct;
#pragma pack(pop)
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(sizeof(TMyStruct));
}
运行结果为 15,即此时这个结构体的字节数为 15 个字节:
产生这个现象的原因是内存里面的数据对齐方式引起的。
对于 C 语言,最基本的整数或浮点数变量按照字节数区分,有1、2、4、8 和 16 个字节的变量,对于 Intel x86 / x64 CPU,其中:
• 1个字节的变量,放在内存的任何位置读写速度都是一样的;
• 2个字节的变量,从偶数地址读写的速度是最快的;
• 4个字节的变量,从 4 的整数倍地址读写的速度是最快的;
• 8个字节的变量,从 8 的整数倍地址读写的速度是最快的;
• 16个字节的变量,从 16 的整数倍地址读写的速度是最快的。
变量按照定义的顺序放在内存里面,按照访问速度最快的数据对齐方式存储:
char a; // 1个字节,放在地址 0
float b; // 4个字节,放在地址 4 - 7
short c; // 2个字节,放在地址 8 - 9
double d; // 8个字节,放在地址 16 - 23
这样整个结构体的存放就是从地址 0 到 23,一共占用了 24 个字节。
如果把对齐方式改为 n,n = 1, 2, 4, 8 或 16,表示:
字节数≤n 的变量按照他们期望的对齐方式,字节数>n 的变量对齐在 n 的整数倍地址上,如果 n = 1,那么就取消了对齐方式,变量可以放在任何地址上 (因为 1 的整数倍地址就是任何地址):
#pragma pack(push) // 记住目前的对齐方式
#pragma pack(n) // 把对齐方式改为 n,n = 1, 2, 4, 8 或 16
// 此处的对齐方式为 n
#pragma pack(pop) // 恢复前面记住的对齐方式
其中 #pragma pack(push) 和 #pragma pack(n) 可以合并为 #pragma pack(push,n)
那么就是这样的:
#pragma pack(push,n) // 记住目前的对齐方式,把对齐方式改为 n,n = 1, 2, 4, 8 或 16
// 此处的对齐方式为 n
#pragma pack(pop)
这样就可以解释以下代码:
#pragma pack(push,1) // 记住目前的对齐方式,把对齐方式改为 1,变量可以放在任何地址上
typedef struct
{
char a; // 假定地址为 0
float b; // 地址为 1 - 4
short c; // 地址为 5 - 6
double d; // 地址为 7 - 14
} TMyStruct; // 整个结构体地址 0 - 14 为 15 个字节
#pragma pack(pop) // 恢复前面记住的对齐方式
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(sizeof(TMyStruct));
}
运行结果是这个结构体的大小为 15,即所有成员的字节数的总和。
C++ Builder 参考手册 ➙ 对齐方式