内存对齐是什么?为什么要对齐?怎么对齐?

看C++源码遇到一个比较陌生的语法:__attribute__((aligned)),例如int x __attribute__ ((aligned (16))) = 0;,秉持着不懂就要问的态度,咨询了下搜索引擎。发现__attribute__是GNU C对ISO C(ISO标准C)的扩展的语法中的一个,它包括了对C、Objective-C、C++的扩展。其实也就是GCC自己扩展了一些它自己能接受的语法,GCC预定义了一个宏__GNUC__,如果检测到定义了这个宏,就说明支持这些语法了。
__attribute__是这些扩展中的一个,用于告诉GCC编译器某些结构所具备的一些属性[1]。而__attribute__((aligned))就是告诉编译器某个变量或者数据结构中的某一成员需要进行数据对齐,例如:

int x __attribute__ ((aligned (16))) = 0;

这个定义要求编译器将变量x按16个字节进行对齐。
__attribute__的问题解决了,但是却又引入了一个新的疑问:内存对齐(memory alignment)。

什么是内存对齐

所谓内存对齐,就是将数据存放到一个是字的整数倍的地址指向的内存之中。处理器在执行指令去操作内存中的数据,这些数据通过地址来获取。不论什么数据都有一定的大小,当一个数据所在的地址和它的大小对齐的时候,就说这个数据自然对齐了(naturally aligned),否则就是没对齐。
怎么理解数据的地址和它的大小对齐这句话呢?例如有一个地址a,它是n的整数倍,并且这个n是2的幂,这时候我们就可以说an个字节对齐了。举个栗子:当 a = 0x00000008,n = 4,22 = 4 的时候,就说地址a和4字节对齐了[2]
套用到数据的地址和它的大小对齐这句话,例如一个long变量,它占用4个字节,也就是他的大小是4字节,如果这个变量的地址为 0x00000004、0x00000008 等,那么这个变量就和4字节对齐了,而如果它的地址是0x00000006 之类的,那么它就没对齐。
下面的表格给出了一个不同数据是否对齐的例子:

Memory alignment

当存取一个地址对齐了它数数据大小的数据结构的时候,就说这是一次对齐存取。上面的例子说的是基本数据类型,当一个复杂的数据结构中的所有成员都自然对齐了,我们就说这个数据结构对齐了,为了能让复杂数据结构对齐,编译器一般会对数据结构做一些填充,使得它的成员都能对齐。

为什么要内存对齐

说了这么多,为什么要进行内存对齐呢?主要有以下几个方面的考量:

  1. 某些处理器只能存取对齐的数据,存取非对齐的数据可能会引发异常;
  2. 某些处理不能保证在存取非对齐数据的时候的操作是原子操作[3]
  3. 相比于存取对齐的数据,存取费对齐数据需要额外花费更多的时钟周期;
  4. 有些处理器虽然支持非对齐数据访问,但是会引发对齐陷阱(alignment trap)[3]
  5. 某些处理只支持简单数据指令非对齐存取,不支持复杂数据指令非对齐存取[4]

总结起来就大概两个大的原因:提升效率和避免出错。

怎么进行内存对齐

很多时候,我们是不需要关注内存对齐的问题的,因为处编译器已经自动为数据进行对齐:对于基本数据类型,编译器会给他们分配一个是他们大小n被的地址;对于组合类型数据结构,编译器会在成员函数中填充一些字节使得每个成员都自然对齐[5]
例如:

struct foo
{
   char a;     // 1 byte
   int b;      // 4 bytes
   short c;    // 2 bytes
   char d;     // 1 byte
} bar;

这个虽然看上去整个结构体只占用了8个字节,但是世界上因为编译器做了填充使得结构体的成员都能对齐,这个结构体实际上占据了12个字节。编译器填充完的结构体如下:

struct foo
{
   char a;            // 1 byte
   char _pad0[3];     // 填充4个字节
   int b;            // 4 bytes
   short c;          // 2 bytes
   char d;           // 1 byte
   char _pad1[1];    // 填充1个字节使得这个结构体的数组所有元素的成员都自然对齐
} bar;

但是有些时候,出于一些特殊的需求,需要人工干预编译器的对齐工作。例如,虽然一个int类型的数据在某些机器上只占了四个字节,但是你却希望它能够按照16个字节对齐,这个时候你就可以使用int x __attribute__ ((aligned (16))) = 0;来指导编译器按照你想要你的对齐方式进行对齐。
了解了对齐,处理让我们知道怎么使用__attribute__ ((aligned ))这个命令意外,有时候也可以指导我们怎么样安排一个数据结构使得空间利用率变高,例如吧上面结构体中的成员的顺序改成下面这样,这个结构体就从原来的占用12个字节变成占用8个字节,节省了30%的空间。如果这个结构体在内存中存在成百上千个,省出来的空间就很客观了。这样不仅能让我们的数据对齐,同时还实现了数据的紧凑。

struct foo
{
   int b;      // 4 bytes   
   char a;     // 1 byte
   short c;    // 2 bytes
   char d;     // 1 byte
} bar;
本文首发于个人微信公众号TensorBoy,微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取最新文章

References


  1. https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html ↩

  2. Memory Alignment ↩

  3. ARM11 MPCore Processor Technical Reference Manual ↩ ↩

  4. http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360f/Beicciei.html ↩

  5. https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=vs-2019 ↩

你可能感兴趣的:(内存对齐是什么?为什么要对齐?怎么对齐?)