内存对齐机制

  内存对齐应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再模糊了。
  不是只有C/C++才会有内存对齐,只要可以跨平台的编程语言都需要做内存对齐,Java、Python都是一样的。

什么是内存对齐?

  理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。如下代码:

//32位系统
#include<stdio.h>
struct{
 int x;
 char y;
}s;

int main()
{
 printf("%d\n",sizeof(s);  // 输出8
 return 0;
}

  现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

为什么要有内存对齐呢?

  尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

  现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

总结为如下两点:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

ps:自然对齐(Natural Alignmnet):是指从一个地址读N个字节,而这个地址可以被N整除(addr % N == 0)。比如在一个以4字节为boundary的系统里,读取地址0x1008开始的4个字节就是对齐的,而读取0x1009开始的4个字节就不是。

内存对齐的情况,如图:


image.png

  一字节的char占用了四个字节,空了三个字节的内存地址,int数据从地址4开始。
此时,直接将地址4,5,6,7处的四个字节数据读取到即可。

没有内存对齐的情况,如图:

image.png

  char型的数据和int型的数据挨在一起,该int数据从地址1开始,那么CPU想要读这个数据的话来看看需要几步操作:

  1. 因为CPU是四个字节四个字节来寻址,首先CPU读取0,1,2,3处的四个字节数据
  2. CPU读取4,5,6,7处的四个字节数据
  3. 合并地址1,2,3,4处四个字节的数据才是本次操作需要的int数据

此时一共需要两次寻址,一次合并的操作。

因此假如没有内存对齐机制,则需多次合并,这样的话工作会十分繁琐。

内存对齐规则

  每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

  有效对其值:min{给定值#pragma pack(n),结构体中最长数据类型长度}。有效对齐值也叫对齐单位

  结构体的总大小为 有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

下面给出几个例子以便于理解:

//32位系统
#include<stdio.h>
struct
{
int i;
char c1;
char c2;
}x1;

struct{
char c1;
int i;
char c2;
}x2;

struct{
char c1;
char c2; 
int i;
}x3;

int main()
{
printf("%d\n",sizeof(x1));  // 输出8
printf("%d\n",sizeof(x2));  // 输出12
printf("%d\n",sizeof(x3));  // 输出8
return 0;
}

  以上测试都是在Linux环境下进行的,linux下默认#pragma pack(4),且结构体中最长的数据类型为4个字节,所以有效对齐单位为4字节。

  一图了然,上面例子三个结构体的内存布局如下:

img

  同理,若#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下:

img

内存对齐岂不是浪费的内存资源么?

  是这样的,但事实上,相对来说计算机内存资源一般都是充足的,我们更希望的是提高运行速度

  编译器一般都会做内存对齐的优化操作,也就是说当考虑程序真正占用的内存大小的时候,也需要认识到内存对齐的影响。

本文引鉴自:

  1. https://baike.baidu.com/item/%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90/9537460?fr=aladdin
  2. https://zhuanlan.zhihu.com/p/30007037
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容