#pragma pack()用法详解

以下内容转载自

https://blog.csdn.net/lime1991/article/details/44536343

1.什么是对齐?为什么要对齐?

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


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

封装类是在内存中将其一个成员直接放在另一个后面,这可能表示对齐部分或全部成员的边界可以小于默认对齐目标体系结构。 pack 提供了数据声明级别的控制。 这与编译器选项 /Zp 不同,该选项只提供模块级别控制。 在杂注出现之后,pack 在第一个 structunion 或 class 声明处生效。pack 对定义没有影响。 在没有参数的情况下调用 pack 会将 n 设置为编译器选项 /Zp 中设置的值。 如果未设置编译器选项,则默认值为 8。

如果更改某个结构的对齐方式,它可能不会使用像内存中一样多的空间,但您可能会发现性能降低或者甚至是因未对齐访问而遇到硬件产生的异常。 您可以使用 SetErrorMode 修改此异常行为。

show (可选)
显示封装对齐的当前字节值。 该值由警告消息显示。

push (可选)
将当前封装对齐值推送到内部编译器堆栈上,并将当前封装对齐值设置为 n。 如果未指定 n,则将推送当前封装对齐值。

pop (可选)
从内部编译器堆栈的顶部移除记录。 如果没有用 n 指定 pop,则与堆栈顶部的生成的记录关联的封装值是新的封装对齐值。 如果指定了 n(例如 #pragma pack(pop, 16)),n 将成为新的封装对齐值。 如果使用 identifier(例如 #pragma pack(pop, r1))进行弹出,则堆栈上的所有记录都将弹出,直到找到包含 identifier 的记录。 该记录将会弹出,与堆栈顶部的生成的记录关联的封装值是新的封装对齐值。 如果使用在堆栈上的任何记录中均未发现的 identifier 进行弹出,则会忽略 pop

identifier (可选)
当与 push 一起使用时,为内部编译器堆栈上的记录指定名称。 当与 pop 一起使用时,从内部堆栈中弹出记录,直到移除 identifier;如果未在内部堆栈上找到 identifier,则不会弹出任何内容。

n(可选)
指定要用于封装的值(以字节为单位)。 如果没有为模块设置编译器选项 /Zpn 的默认值为 8。 有效值为 1、2、4、8 和 16。 成员将在作为 n 的倍数或成员的大小的倍数的边界(以较小者为准)上对齐。

#pragma pack(pop, identifier``, n``) 是不确定的。

有关如何修改对齐方式的详细信息,请参阅以下主题:

  • __alignof

  • align

  • __unaligned

  • 结构对齐示例(特定于 x64)

    System_CAPS_ICON_warning.jpg 警告

    请注意,在 Visual Studio 2015 和更高版本可使用标准的 alignas 和 alignof 运算符,这 不同于 __alignof 和 declspec( align ),它们是跨编译器可移植的。 C++ 标准不能解决封装,因此仍必须使用 pack(或其他编译器上相应的扩展)来指定小于目标体系结构字体大小的对齐方式。

以下示例演示如何使用 pack 杂注更改结构的对齐方式。

// pragma_directives_pack.cpp  
#include <stddef.h>  
#include <stdio.h>  
  
struct S {  
   int i;   // size 4  
   short j;   // size 2  
   double k;   // size 8  
};  
  
#pragma pack(2)  
struct T {  
   int i;  
   short j;  
   double k;  
};  
  
int main() {  
   printf("%zu ", offsetof(S, i));  
   printf("%zu ", offsetof(S, j));  
   printf("%zu\n", offsetof(S, k));  
  
   printf("%zu ", offsetof(T, i));  
   printf("%zu ", offsetof(T, j));  
   printf("%zu\n", offsetof(T, k));  
}  

0 4 8  
0 4 6  

以下示例演示如何使用 pushpop 和 show 语法。

// pragma_directives_pack_2.cpp  
// compile with: /W1 /c  
#pragma pack()   // n defaults to 8; equivalent to /Zp8  
#pragma pack(show)   // C4810  
#pragma pack(4)   // n = 4  
#pragma pack(show)   // C4810  
#pragma pack(push, r1, 16)   // n = 16, pushed to stack  
#pragma pack(show)   // C4810  
#pragma pack(pop, r1, 2)   // n = 2 , stack popped  
#pragma pack(show)   // C4810  


3.1 基本数据类型所占内存大小




以下例子均按32bit编译器处理。

3.2 Test1

  1. #pragma pack(4)
  2. struct Test1
  3. {
  4. char c;
  5. short sh;
  6. int a;
  7. float f;
  8. int *p;
  9. char *s;
  10. double d;
  11. };

总共占28Bytes。 c的偏移量为0,占1个Byte。sh占2个Byte,它的对齐模数是2(2<4,取小者),存放起始地址应该是2的整数倍,因此c后填充1个空字符,sh的起始地址是2。a占4个Byte,对齐模数是4,因此接在sh后存放即可,偏移量为4。f占4个字节,对齐模数是4,存放地址是4的整数倍,起始地址是8。p,s的起始地址分别是12,16。d占8个字节,对齐模数是4(4<8),d从偏移地址为20处存放。存放后结构体占28个字节,是4的整数倍不用补空字符。



  1. struct Test2
  2. {
  3. char c;
  4. double d;
  5. int a;
  6. short sh;
  7. float f;
  8. int *p;
  9. char *s;
  10. };

将Test1个变量的顺序换一下位置,结构体Test2占用内存32Byte,可见写结构体时,将各个变量按所占内存从小到大排列所占结构体所占内存较小。


 
 

3.3关于静态变量static

静态变量的存放位置与结构体实例的存储地址无关,是单独存放在静态数据区的,因此用siezof计算其大小时没有将静态成员所占的空间计算进来。

  1. #pragma pack(4)
  2. struct Test3
  3. {
  4. char c;
  5. short sh;
  6. int a;
  7. float f;
  8. int *p;
  9. char *s;
  10. double d;
  11. static double sb;
  12. static int sn;
  13. };

sizeof(Test3)=28

3.4关于类

空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。

(一)类内部的成员变量:

  • 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
  • static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

(二)类内部的成员函数:

  • 普通函数:不占用内存。
  • 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的

  1. #pragma pack(4)
  2. class cBase{};

sizeof(cBase)=1

3.4.1 不包含虚函数的类

  1. #pragma pack(4)
  2. class CBase1
  3. {
  4. private:
  5. char c;
  6. short sh;
  7. int a;
  8. public:
  9. void fOut(){ cout << "hello" << endl; }
  10. };
不包含虚函数时,对于类中的成员变量按结构体对齐方式处理,普通函数函数不占内存。sizeof(CBase1)=8

3.4.2 包含虚函数的类

  1. #pragma pack(4)
  2. class CBase2
  3. {
  4. private:
  5. char c;
  6. short sh;
  7. int a;
  8. public:
  9. virtual void fOut(){ cout << "hello" << endl; }
  10. };
包含虚函数时,类中需要保存虚函数表的入口地址指针,即需要多保存一个指针。这个值跟虚函数的个数多少没有关系。sizeof(CBase2)=12

3.4.3 子类

子类所占内存大小是父类+自身成员变量的值。特别注意的是,子类与父类共享同一个虚函数指针,因此当子类新声明一个虚函数时,不必在对其保存虚函数表指针入口。
  1. #pragma pack(4)
  2. class CBase2
  3. {
  4. private:
  5. char c;
  6. short sh;
  7. int a;
  8. public:
  9. virtual void fOut(){ cout << "virtual 1" << endl; }
  10. };
  11. class cDerive : public CBase
  12. {
  13. private :
  14. int n;
  15. public:
  16. virtual void fPut(){ cout << "virtual 2"; }
  17. };

sizeof(cDerive)= sizeof(cBase)+sizeof(int n) = 16



  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值