#pragma pack(n)对齐用法详解

什么是对齐,以及为什么要对齐?

        现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。 

对齐的实现 
        通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择时候目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。 但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。

语法:
#pragma pack( [show] | [push | pop] [, identifier], n )

用法说明

pack 为 struct, union 和 class 等的成员对齐指定字节边界. 与编译选项(属性 -> 配置属性 -> C/C++ ->代码生成 -> 结构成员对齐)的 /Zp 开关不同, 它不针对整个项目, 而仅针对模块, 比如一个编译单元.

语法具体分析:
1、#pragma pack(show)

     显示当前packing aligment的字节数,以warning message的形式被显示;

2、#pragma pack(n)

     指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

3、#pragma pack() 
    将当前字节对齐值设为默认值(通常是8) .

4. #pragma pack(push) 
     将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;

5、#pragma pack(pop)

     从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;

6、#pragma pack(push, n
    先将当前字节对齐值压入编译栈栈顶, 然后再将 n 设为当前值.

7、#pragma pack(pop, n) 
    将编译栈栈顶的字节对齐值弹出, 然后丢弃, 再将 n 设为当前值.

8. #pragma pack(push, identifier) 
    将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier . 
9. #pragma pack(pop, identifier) 
    将编译栈栈中标识为 identifier 位置的值弹出, 并将其设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃. 
10. #pragma pack(push, identifier, n) 
    将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier, 再将 n 设为当前值. 
11. #pragma pack(pop, identifier, n) 
    将编译栈栈中标识为 identifier 位置的值弹出, 然后丢弃, 再将 n 设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃.

 

(identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;)

 

重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;

2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;

3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;

4,复杂类型(如结构)整体的对齐<注意是“整体”>是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;

5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;

6、结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。

7、数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

 补充一下,对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.

应用实例:

#include 
struct A 
{ 
     int      a; 
     char    b; 
     short c; 
}; 


 int main()
{
    cout << sizeof(A)

显示sizeof(A)为8;

分析:a的对齐长度为4字节,存放地址:0x0000 ~ 0x0003;

           b的对齐长度为1字节,存放地址:0x0004

           c的对齐长度为2字节,   存放地址:0x0006 ~ 0x0007;

第a、b变换位置

 

#include 
struct A 
{ 
     char    b; 
     int      a; 
     short c; 
}; 


 int main()
{
    cout << sizeof(A)
 

显示sizeof(A)为12;

分析:

           b的对齐长度为1字节,存放地址:0x0000

a的对齐长度为4字节,存放地址:0x0004 ~ 0x0007;

           c的对齐长度为2字节,   存放地址:0x0008 ~ 0x0009;

A的对齐方式为4,所以sizeof(A)为4的整数倍:12

#include 


#pragma pack(push) //保存对齐状态
#pragma pack(2)

struct A 
{ 
     char    b; 
     int      a; 
     short c; 
}; 
#pragma pack(pop) //恢复对齐状态

 int main()
{

    cout << sizeof(A)
 
分析:
这里指定了对齐长度为2;

b的对齐长度为1字节,存放地址:0x0000

a的对齐长度为2字节,存放地址:0x0002 ~ 0x0005;//数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

c的对齐长度为2字节,   存放地址:0x0006 ~ 0x0007;

A的对齐方式为2,(这里8是2的倍数,因此不用补0)

所以sizeof(A)为2的整数倍:8

 

#include 


#pragma pack(push) //保存对齐状态
#pragma pack(1)

struct A 
{ 
     char    b; 
     int      a; 
     short c; 
}; 
#pragma pack(pop) //恢复对齐状态

 int main()
{

    cout << sizeof(A)
 
分析:
这里指定了对齐长度为1;

b的对齐长度为1字节,存放地址:0x0000

a的对齐长度为1字节,存放地址:0x0001 ~ 0x0004;

c的对齐长度为1字节,   存放地址:0x0005 ~ 0x0006;

A的对齐方式为1,(这里7是1的倍数,因此不用补0)

所以sizeof(A)为4的整数倍:7


当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。

可以这样理解,  __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值