[OC底层]内存字节对齐

1,107 阅读3分钟

1:什么是内存对齐

首先看一个小例子:

struct {
    int x;
    char y;
} s1;

int main(int argc, const char * argv[]) {

    printf("%lu\n", sizeof(s1));
    
 }

输出为:8

在Mac系统中 int占4个byte, char占1个byte, 那么把它们放在结构体里应该站在4 + 1 = 5byte. 但是结构是8byte, 这就是由于字节对齐导致的.

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

2:为什么要内存对齐

为了内存获取速度更快

实际内存读取的时候,是内存单元每n个为一组,一次读一组, n称为对齐系数.

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。 拿上面的结构体为例, 假设n=4的时候 1, 2, 3, 4存的是int类型的x, 5号内存存的是char y. 如下体所示 Screenshot 2021-08-01 at 15.50.33.png

  • x的时候只需要拿出第一组14号内存, 读出x; Screenshot 2021-08-01 at 16.02.29.png
  • y的时候拿出58号内存, 然后扣出5号内存读出来; Screenshot 2021-08-01 at 16.02.38.png 那么如果n=2的时候,会发生什么呢,如图所示: Screenshot 2021-08-01 at 16.15.58.pngx的时候就要先读出1,2号内存, 再读出3,4号存, 然后再拼到一起. 这样就会造成读了两次内存,对于CPU来说,内存访问速度很慢,访问两次就会浪费很多时间.

3:对齐的规则

  • 数据成员对齐规则:(Struct或者Union的数据成员)第一个数据成员放在偏移为0的位置。以后每个数据成员的位置为min(对齐系数,自身长度)的整数倍,下个位置不为本数据成员的整数倍位置的自动补齐。

  • 数据成员为结构体:该数据成员的内最大长度的整数倍的位置开始存储。

  • 整体对齐规则:数据成员按照1,2步骤对齐之后,其自身也要对齐,对齐原则是min(对齐系数,数据成员最大长度)的整数倍。

struct {
    double a;

    char b;

    int c;
    
    short d;
} s1;

struct {
    double a;

    int c;

    char b;

    short d;
} s2;

int main(int argc, const char* argv[]) {

    printf("%lu\n", sizeof(s1));

    printf("%lu\n", sizeof(s2));
  
}

输出: s124, s216

s1的内存结构:

Screenshot 2021-08-01 at 18.32.30.png

  • a 是double类型, 占8个字节的空间,偏移量为0
  • b 是char类型, 占1个字节空间, 按照规则,b 的偏移量是char大小的整数倍,当前的偏移量是8,是其偏移量的整数倍, b紧跟在a的后面
  • c 是int类型,占4个字节的空间,根据规则1, 4的偏移量必须是int的整数倍,所以编译器会在b后面插入三个字节缓冲区
  • d是short类型, 占2个字节空间, d的偏移量正好是16个字节
  • 此时s1 大小为18个字节, 根据规则3, 结构体s1的大小必须是其最大成员double的整数倍, 所以最后s1所占内存大小事18+6 = 24

s2的内存结构:

Screenshot 2021-08-01 at 19.07.11.png

References: