#pragma pack详解

数据对齐

程序中变量的存储可以直接影响到程序的运行速度。计算机中都是以字节划分内存空间,通常编译器会为我们选择适合目标平台的对齐策略,但是有时候也带了一些麻烦,要自定义变量的对齐策略我们就需要用到#pragma pack

应用场景

百度百科里提到一个典型的应用场景:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。

说白了,一个正确的字节对齐方式,就是为了让CPU在最短的时间内读取完变量,同时还让整体的结构存储空间最小。

字节对齐方式的使用

四个概念
  • 数据类型自身的对齐值:基本数据类型的自身对齐值。
  • 结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。
  • 指定对齐值:系统默认的或者我们通过#pragma pack(value)指定对齐值value。
  • 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

想要巧妙设定字节对齐的字节数,必须要知道上面四个数值之间的关系:数据成员、结构体和类的有效对齐值,是最终存储时的数据、结构体和类决定数据存放地址方式的值。分为数据有效对齐值,结构体和类有效对齐值两个。同时整体的要满足这四条规则。

  1. 数据有效对齐值 = min(数据类型的自身对齐值,指定对齐值value)。
  2. 结构体,联合体和类有效对齐值 = min(结构体、联合体和类自身对齐值,指定对齐值value)
  3. 保证每一个数据满足:不同类型数据都满足:当前数据存储的首地址 % 数据有效对齐值 = 0;
  4. 补齐末尾的空间,保证长度对齐,即总长度 % 实际对齐长度 = 0;
选择最好的对齐字节数值

最好的value的值,就是和变量自身对齐值为整数数关系下,使得总体数据结构占用空间最小的值。

//默认字节对齐方式为4字节对齐
struct C {
    char b;  //自身对齐值为1,有效对齐值为min(1,4)
    int a;   //自身对齐值为4,有效对齐值为min(4,4)
    short c;  //自身对齐值为2,有效对齐值为min(2,4)
};
//由上述第2条:C的有效对齐值为4。

数据结构大小计算:

  • 我们认为第一个元素的起始地址为0x0000,所以,第一个char b的有效对齐值为1,0x0000%1=0,符合。
  • 第二个元素int a,有效对齐值为4,初始位置为0x0001,0x0001%4=1,不符合,往后面移动一位,0x0002%4=2,不符合,到0x0004,0x0004%4=0,符合,所以int a的起始地址为0x0004。
  • 第三个元素short c,有效对齐值为2,起始地址为0x0008,0x0008%2=0,符合,所以short c起始地址为0x0008
  • 所以结构体C的整体存储空间大小为:4+4+2=10,10%4=2,所以要满足上面的第四条,则0x0010和0x0011也是C的空间,这样C一共就占了12个字节。

但是明显这个不是最优的字节对齐方式,看看下面这个:

//改变字节对齐方式为2字节对齐
struct C {
    char b;  //自身对齐值为1,有效对齐值为min(1,2)
    int a;   //自身对齐值为4,有效对齐值为min(4,2)
    short c;  //自身对齐值为2,有效对齐值为min(2,2)
};
//由上述第2条:C的有效对齐值为2。

数据结构大小计算:

  • 第一个元素的起始地址为0x0000,char b的有效对齐值为1,0x0000%1=0,符合。
  • 第二个元素int a,有效对齐值为2,初始位置为0x0001,0x0001%2=1,不符合,往后面移动一位,0x0002%2=0,符合,所以int a的起始地址为0x0002。
  • 第三个元素short c,有效对齐值为2,起始地址为0x0006,0x0006%2=0,符合,所以short c起始地址为0x0006。
  • 所以结构体C的整体存储空间大小为:2+4+2=8,8%2=0,所以满足上面的第四条,这样C一共就占了8个字节。

所以,设置不同的字节对齐方式对于数据的存储空间来说有不同的影响,在和变量自身对齐值为整数数关系下,选择变量有效对齐值中位数附近的数值效果最好。

#pragma pack

作用:

指定结构体,联合,类成员的字节对齐方式

语法:
#pragma pack( show | push | pop ,identifier, n )
  • show:
    • #pragma pack(show):显示当前字节对齐方式的字节数,以warning显示。
  • push:
    • #pragma pack(push):将当前的字节对齐方式入栈(internal compiler stack),也就是保存当前的字节对齐方式。
  • pop:
    • #pragma pack(pop):将(internal compiler stack)栈顶的记录出栈,也就是恢复最新的一次字节对齐方式。
  • identifier:
    • #pragma pack(push,identfier):当同push一起使用时,赋予当前被压入栈中的record一个名称;
    • #pragma pack(pop,identfier):当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作。
  • n:
    • #pragma pack(n):指定n个字节对齐,合法数值为2的非负整数次幂: 1、2、4、8、16,缺省时为8。
    • #pragma pack(push,n):当同push一起使用时,将当前的字节对齐方式入栈,同时设置当前的字节对齐数值为n
    • #pragma pack(pop,n):当同pop一起使用时,将栈顶的字节对齐方式出栈,同时设置当前的字节对齐数值为n
使用用例

设置字节对齐方式为2字节对齐:

#pragma pack(2)
struct C {
    char b;  //自身对齐值为1,有效对齐值为min(1,2)
    int a;   //自身对齐值为4,有效对齐值为min(4,2)
    short c;  //自身对齐值为2,有效对齐值为min(2,2)
};
#pragma pack()//恢复默认字节对齐方式
  • 21
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值