C/C++语言内存对齐

内存对齐:在计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任何类型的变量。但实际上在访问特定类型的变量的时候需要从特定的地址开始,这就需要各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的存放,这就是内存对齐,也叫字节对齐。

内存对齐的作用:

  • 可移植性:因为不同平台对数据的在内存中的访问规则不同,不是所有的硬件都可以访问任意地址上的数据,某些硬件平台只能在特定的地址开始访问数据。所以需要内存对齐。
  • 性能原因:一般使用内存对齐可以提高CPU访问内存的效率。如32位的intel处理器通过总线访问内存数据,每个总线周期从偶地址开始访问32位的内存数据,内存数据以字节为单位存放。如果32为的数据没有存放在4字节整除的内存地址处,那么处理器需要两个总线周期对数据进行访问,显然效率下降很多;另外合理的利用字节对齐可以有效的节省存储空间。

默认内存对齐影响因素:与平台架构(位数)和编译器的默认设置有关。

总线周期:CPU通过总线和存储器或者IO设备进行一次数据传输需要的时间,通常为四个或者多个时钟周期组成。

内存对齐规则

  1. 整体类型的对齐规则:若设置了内存对齐为m个字节,类中的最大成员的对齐字节为n,则该数据类型的对齐字节为p=min(m,n)。(一般32位机器的默认pack为4位;64位机器的默认pack为8位,程序中可以显式设置pack的大小)
  2. 类型中成员的对齐规则:类中的第一个成员放在offset为0的位置;对于其他成员,若设置了内存对齐为m个字节,假设该数据成员的对齐字节数(即当前成员所占的字节数)为k,则该数据成员的起始位置是min(m,k)的整数倍。
  3. 整体对齐规则:最后整个类型的大小为p=min(m,n)的整数倍。
  4. 当设置对齐字节数大于类中最大成员的对齐字节数的时候,这个设置实际不产生任何效果;当设置对齐字节数为1时,类的大小就是简单的把所有成员大小相加。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <stddef.h>
using namespace std;
//前提条件:32位机器,当前编译器默认pack为4
struct T {
int a;
short b;
int c;
double d;
};
int main() {
cout << offsetof(T, a) << endl
<< offsetof(T, b) << endl
<< offsetof(T, c) << endl
<< offsetof(T, d) << endl;
return 0;
}

根据上面的分析:

  • a为第一个成员,offset为0。
  • b为short,对齐字节数为2,所以其对齐字节为min(2,4)=2,故offset为4
  • c为int,对齐字节数为4,所以其对齐字节数为min(4,4)=4,故offset为8
  • d为double,对齐字节数为8,故对齐字节数为min(4,8)=8,故offset为12
  • 总的大小为20,是,min(8,4)的倍数.

使用pragma pack修改系统默认pack

修改系统的默认pack可以使用系统函数pragma的pack参数,但是修改之后的pack一定是2的n次幂

1
2
3
#pragma pack(16) //修改pack修改为16
#pragma pack() //恢复系统的默认pack
#pragma pack(show) //返回系统当前的pack,由警告信息显示,注意gcc不支持。只有VS支持

此外pack还有push,pop其他参数可选,但是不同的编译器对这些参数的实现有不同的含义,如果需要了解可以参考对应的资料。