C++内存对齐

引言

内存对齐有利于从硬件层面提高cpu对内存的访问效率。通常情况下,cpu取数据是按照内存单元去取的。这就意味着,内存对齐的情况下,cpu取某个地址上的数据(它存储在某个内存单元),假设cpu一次只取一个内存单元的数据,那么此时cpu只需要访问一次内存即可。但是,如果内存非对齐,这个数据就有可能落在两个内存单元,那cpu就需要至少访问两次内存。由此可见,内存不对齐的情况下,访存效率降低。

基本数据类型的对齐

以下结果是在64位系统中测试的:

数据类型大小(字节)
short2
char1
int4
float/double8
int*8
long8
long long8

对于32位系统,int*和long类型占用4字节。另外,数组是按照数组中元素的类型去对齐的,比如int[]就按照4字节对齐。

类或者结构体的对齐

首先看下面一段代码:

class A{
public:
  int* a;
  char b;
  short c;
  A() { a = new int(3); }
  ~A() { delete a; }
};

对于类或者结构体的对齐,首先考虑内部各变量自身的对齐,其次再考虑类作为一个整体,也需要按照最大的数据成员去对齐。所以综合这两个对齐要求,类A对齐之后的大小为16字节。那么A中成员变量在内存中具体是如何对齐的呢,可以参考下图:
在这里插入图片描述
成员a是一个指针,按照8字节对齐,其首地址为0,可以对齐。此时,b的偏移地址为8,b也可以对齐。b占用一个字节,那么c的偏移地址就是9,但c要按照2字节对齐,所以要填充一个字节,也就是x所占的位置,因此,c的偏移地址变成了10,c占用2字节,那么A的大小是12字节。但是,A需要按照8字节对齐,所以再填充4字节,总共占用16字节。
再来看看如下代码:

class A{
public:
  char b;
  int* a;
  short c;
  A() { a = new int(3); }
  ~A() { delete a; }
};

可以看到,调换了下成员变量的位置,直觉告诉我们类A的大小应该不变,但是实际上其大小变为了24字节。可以参考上述过程计算一遍。由此可见,数据成员的排列也会影响到内存对齐之后的结果。这里有一个原则,就是尽量将对占用空间大的成员声明在前面,使得内存更加紧凑。

如何控制内存对齐

控制内存对齐的方式有两种:(1)#pragma pack(size);(2)alignas(size)。size值必须是2、4、8、16…这种,两者的区别在于,pack可以设置不进行内存对齐,也即1字节对齐。并且其设置的对齐大小不受类或结构体成员的限制。而alignas设置的对齐大小不一定生效,对于上述类A,即使设置了alignas(4),而A.a的大小为8字节,所以还是按照8字节对齐,也即按照alignas和数据成员中最大的size进行对齐。另外,与alignas对应的,alignof(T),可以获取类型T的大小,如alignof(int)的值为4。

// 按照16字节对齐,对齐之后类B的大小为48字节
class alignas(16) B{
public:
	int a;
	int* b;
	char c[20];
	char d;
};

// 设置不进行内存对齐
#pragma pack(1) 
// C的大小为33字节
struct C{
	int a;
	int* b;
	char c[20];
	char d;
};
#pragma pack() // 恢复内存对齐

通常情况下,我们不用设置内存对齐,只有在一些特殊场景下。比如网络通信,因为通信双方可能在不同的平台上运行程序,内存对齐的方式也可能不同,如果不去控制内存对齐的话,那么双方在解析收到的数据时,就有可能得不到正确的结果。因此双方会协定好对齐方式,比如取消内存对齐,也即按照1字节对齐。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值