C/C++ 的结构,就像是一个将几种数据结构打包的功能。在使用的时候,可能会注意到,结构体的大小不等于结构体所有成员的大小之和,原因是编译器进行了字节对齐。
# 字节对齐的目的
如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在 32 位 CPU 下,假设一个整型变量的地址为 0x00000004
(下面简写为 0x04
),那它就是自然对齐的。
需要字节对齐的根本原因在于 CPU 访问数据的效率问题。32 位 CPU 把内存的每 4 个字节分为一组,一次可以同时访问一组。比如,可以同时访问 0x00 - 0x03
,也可以同时访问 0x04 - 0x07
,但不能同时访问 0x03 - 0x06
。
若不进行字节对齐,假如一个 int 存储在了 0x03 - 0x06
,那么读取这个 int 需要 CPU 读取两次内存,降低了效率。而进行了字节对齐,效率会有一定的提升。
这段话也解释了,64 位系统相比 32 位系统更大(由于字节对齐,需要更多的空位),但是运行速度会快一些(对于 8 字节的数据类型,如 long long
和 double
的读取会快一些)。
# C 语言编译器对字节对齐的要求
- 标准数据类型:只要地址是它的长度的整数倍就行了;
- 数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
- 联合:按其包含的长度最大的数据类型对齐。
- 结构体:结构体中每个数据类型都要按其包含的长度最大的数据类型对齐。
如
struct foo
{
char c10[10];
long long ll;
char c;
};
int main()
{
std::cout << sizeof(foo); //输出 32
}
该结构中,c10
虽然只占 10 个字节,但是由于它要对齐 8 字节的 long long,因此会在 c10
后填充到 16 个字节(填充了 6 字节)。同样的, c
后面也会填充到 8 字节。
整个结构占了 16 + 8 + 8 = 32 字节。
把上述代码的 long long 改为 int,整个结构占了 12 + 4 + 4 = 20 字节。
struct foo
{
char c10[10];
int i;
char c;
};
int main()
{
std::cout << sizeof(foo); //输出 20
}
# 手动设置字节对齐
在设计不同CPU下的通信协议时,或者编写硬件驱动程序时,寄存器的结构这两个地方都需要按统一字节对齐,所以需要手动设定字节对齐。
可以使用 #pragma pack()
语句设置字节对齐,如下:
#pragma pack (1) /*指定按2字节对齐*/
struct foo
{
char c10[10];
int i;
char c;
}bar;
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
int main()
{
std::cout << sizeof(foo); //输出 15
}
结构体占了 10 + 4 + 1 = 15 字节。