字节对齐
现在计算机系统的内存都是按照字节划分的,理论上对任何变量的访问可以从任何地址开始,但是实际情况是访问特定的变量经常在特定的内存地址访问,这就需要各种类型的数据按照一定的规则在空间上进行排列,而不是顺序排放,这就是对齐的概念;
比如在32bit system 下,一个4字节的int,如果它的地址是0x00000004 (4 的倍数),那么它就是对齐的;如果是0x00000002(非4 的倍数),那么它就是非对齐的
- 为什么要使用字节对齐?
字节对齐的根本原因是在于CPU访问效率的问题,上面的例子中如果地址时0x00000002,那么CPU 需要访问两次内存,
第一次访问0x000000002 ~ 0x00000003 得到一个2 byte 的内容;
第二次访问0x000000004 ~ 0x00000005 在得到一个2 byte的内容,组合得到整形数据
如果变量在对齐位置上,就可以一次取出数据,一些系统对对齐比较严格,比如spar 系统,取未对齐的数据就会发生错误;在x86 上不会产生错误,只是效率下降;不同的对齐方式,在内存中存储的方法可能不一样,合理利用字节对齐,可以有效节约存储空间
结构体对齐
结构体对齐的规则首先要看有没有用#pragma pack宏声明,使用这个预编译指令可以改变对齐规则,
有宏定义的情况下结构体自身的大小应为预编译指定规定对齐的大小的整数倍,同时结构体内的成员的地址也会按照预编译指定规定的大小对齐,#pragma pack 参数可以是 "1" "2" "4" "8" 或者 "16"
未使用pragma pack宏声明
未使用pragma pack宏声明的情况下,遵循下面三个原则:
- 第一个成员的首地址假设为0
- 每个成员的首地址大小是其自身大小的整数倍
- 结构体的总大小,是其成员中所包含的最大类型size大小的整数倍
示例一:
// struct = 1+2+4+8
struct demostruct {
// baseAddr length padding
uint8_t a; // addr 1 1
uint16_t b; // addr+2 2 0
uint32_t c; // addr+4 4 0
uint64_t d; // addr+8 8 0
};
- 上面的例子中如果没有结构体的内存对齐,真实的大小为 1 + 2 + 4+ 8 = 15 字节
按照结构体里成员地址需要是其自身大小的整数倍的原则 假定为结构体首地址为 addr
- 第一个成员 a 字节首地址 addr,长度为 1
- 第二个成员 b 字节首地址理论上为 addr + 1,但是按照地址对齐原则,地址必须是 2 的整数倍,所以在 a 后补充一个字节,b的首地址变为 add + 2,长度为 2
- 第三个成员 c 首地址理论上为 addr + 4,符合是自身长度整数倍的原则,长度为 4
- 第四个成员 d 首地址 addr + 8,符合是自身长度整数倍的原则
- 结构体大小为 16字节,结构体内包含最大成员占用 8 字节,符合上述原则
打印出的结构体大小和各成员内存地址如下:
struct_a size 16 a addr:008726E8 b:008726EA c addr:008726EC d addr:008726F0
示例二:
// struct = 1+8+1+1
struct demostruct_b {
// baseAddr length padding
// addr 1 7
// addr+8 8 0
// addr+16 1 0
// addr+17 1 0
uint8_t a;
uint64_t b;
uint8_t c;
uint8_t d;
};
- 上面的例子中如果没有结构体的内存对齐,真实的大小为 1 + 1 + 8+ 1 = 11 字节
- 第一个成员 a 字节首地址 addr,长度为 1
- 第二个成员 b 字节首地址理论上为 addr + 1,但是按照地址对齐原则,地址必须是 8 的整数倍,所以在 a 后补充7个字节,首地址变为 add + 8 长度为 8,
- 第三个成员 c 首地址为 addr + 16,长度为 1
- 第四个成员 d 首地址 addr + 17,长度为 1
- 结构体大小为 18 字节,结构体内包含最大成员占用 8 字节,所以还需要在最后补充 6 字节,共 24 字节
打印出的结构体大小和各成员内存地址如下:
struct_b size 24 a addr:008726F8 b:00872700 c addr:00872708 addr:00872709
使用#pragma pack宏声明
#pragma pack(push)
#pragma pack(4)
// struct = 1+8+1+1
struct demostruct_c {
// baseAddr length padding
// addr 1 3
// addr+4 8 0
// addr+12 1 0
// addr+13 1 0
uint8_t a;
uint64_t b;
uint8_t c;
uint8_t d;
};
#pragma pack(pop)
- 上面的例子中用宏定义 按照 4 字节的方式对齐
- 第一个成员 a 字节首地址 addr,长度为 1
- 第二个成员 b 字节首地址理论上为 addr + 1,但是按照地址对齐原则,地址必须是 4 的整数倍,所以在 a 后补充3个字节,首地址变为 add + 4 长度为 8,
- 第三个成员 c 首地址为 addr + 12,长度为 1
- 第四个成员 d 首地址 addr + 13,长度为 1
- 结构体大小为14 字节,不是 pack(4) 的整数倍,所以在最后补充 2 字节,共 16 字节
struct_c size 16 a addr:00872710 b:00872714 c addr:0087271C addr:0087271D
pack对齐方法01.jpg注意:
当#pragma pack 设定的 pack 值大于结构体中最大成员的size 值时,应当以结构体中最大成员的size作为 pack 的值
换句话说,应该以这两者中较小的值作为 pack 的值
#pragma pack(push)
#pragma pack(8)
// struct = 1+4 +2
struct demostruct_d {
// baseAddr length padding
// addr 1 3
// addr+4 4 0
// addr+8 2 0
uint8_t a;
uint32_t c;
uint16_t b;
};
#pragma pack(pop)
验证结果是12 字节,不是以设定的 pack 值 8
*struct_d size 12 a addr:00EF2720 b:00EF2728 c addr:00EF2724
网友评论