Loading

字节对齐

概念

现代计算机中内存空间都是按照字节(byte)划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如4字节的 int 型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即对齐跟数据在内存中的位置有关。

为什么要对齐

  • 一些平台严格要求,比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,
  • 为了使CPU能够对变量进行快速的访问

如果0x02~0x05存了一个 int ,读取这个 int 就需要先读0x01~0x04,留下0x02~0x04的内容,再读0x05~0x08,留下0x05的内容,两部分拼接起来才能得到那个 int 的值,这样读一个 int 就要两次内存访问,效率就低了。

pragma pack

sizeof 运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题,有时候为了内存对齐需要补齐空字节。通常写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。

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

作用:指定结构,联合和类的包对齐方式(pack alignment)

#pragma pack(4)    //通知编译器 4字节对齐

字节对齐规则

结构体中各个成员按照它们被声明的顺序在内存中顺序存储。对齐规则如下:

  1. 各个数据成员按各自数据类型对齐

    对齐模数是【该数据成员所占内存】与【#pragma pack指定的数值】中的较小者

  2. 整个结构体字节大小向结构体模数对齐

    结构体模数是【#pragma pack指定的数值(32位机默认4字节、64位机默认8字节)】和【结构体内部最大的基本数据类型成员】长度中数值较小者。结构体的长度应该是该模数的整数倍。

基本数据类型】无符号字节数一样。32位机默认4字节对齐、64位机默认8字节对齐。(实践数据:linux 64位机默认16字节对齐、linux 32位机默认4字节对齐)。win64未验证。

char bool short int long float double long long long double 指针
Win-32 长度 1 1 2 4 4 4 8 8 8 4
模数 1 1 2 4 4 4 8 8 8 4
Linux-32 长度 1 1 2 4 4 4 8 8 12 4
模数 1 1 2 4 4 4 4 4 4 4
Linux-64 长度 1 1 2 4 8 4 8 8 16 8
模数 1 1 2 4 8 4 8 8 16 8

样例

内部数据成员都是以基本数据类型进行对齐。

typedef struct A {
	double val;		// 8 bits
	double val2;	// 8 bits
} A;

typedef struct B {
	int m_i;			// 4 bits	
	A m_A;				// 16 bits
	double m_d;	// 8 bits
	int m_i2;			// 4 bits
} B;

b.m_int = 0x61fed8

b.m_A.val = 0x61fee0

b.m_A.val2 = 0x61fee8

b.m_double = 0x61fef0

b.m_int2 = 0x61fef8

sizeof(A) = 16

sizeof(B) = 40

字节对齐-样例.jpg

static静态数据

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

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

  • 类内成员变量

    • non-static变量:要占用内存,参考上面对齐规则
    • static变量:不占用内存,存储在全局/静态存储区
  • 类成员函数

    • 普通函数:不占内存

    • 虚函数:4字节(32位),虚表指针(vptr),指向虚函数表(vtbl)。并且,虚表指针存放在第一个4字节

      • 一个类的虚函数所占用的地址是不变的,和虚函数的个数没有关系。
      • 子类与父类共享一个虚表指针
      class CBase {
      public:
      	int a; // 4 bits
      public:
      	virtual void fOut(){ cout << "virtual base" << endl; }
      };
      
      class CDerive : public CBase {
      public:
      	int n; // 4 bits
      public:
      	virtual void fPut(){ cout << "virtual derive"; }
      };
      
      // 打印地址
      CDerive derive;
      cout << sizeof(derive) << endl;	// 12
      cout << &derive << endl;		// 0x61fe9c 
      cout << &derive.a << endl;		// 0x61fea0 
      cout << &derive.n << endl;		// 0x61fea4 
      

参考

#pragma pack()用法详解

posted @ 2021-01-25 14:15  JakeLin  阅读(524)  评论(0编辑  收藏  举报