最近学了C高级里的结构体内存对齐,所以在这里分享一下

要想理解内存对齐,我们要先知道计算机是如何使用内存的

使用内存

计算机为了快速读写数据,在默认情况下是将数据存放在

地址 能被 该数据类型大小 整除的起始位置

我举个例子可能就明白了:

一个int占4个字节,那么该int就默认存储在地址能被4整除的起始位置

我们假设,程序新开辟了一块空间,起始地址为 0x100

在这之前存了一个char 类型的数据,起始位置在0x100 大小为1

再存int时,起始位置只能在被4整除的地址,就如下图,只能存在0x104

所以中间的0x1010x103的空间就被浪费了,所以所占空间为 8 字节

内存对齐

了解完计算机是如何使用内存的,就可以来了解一下内存对齐了,内存对齐的机制与具体的编译器相关,但是一般来说满足三个准则:

  1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

  2. 结构体每个成员相对于结构体首地址的偏移量都是当前成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;

  3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

简单来说,就是要先找到结构体中最宽的成员的大小,然后以这个大小为基准,去确定该结构体的首地址以及其他成员对齐的基准。

一般来说,我们只需要考虑准则 2、3即可,准则1是计算机需要考虑的。

文字可能看起来不好理解,所以我们来看几个例子应该就懂了

struct s1
{  
    float f;  // 4
    char c;  // 1
    int a[3];  // 4
};

在结构体s1中,最宽成员的大小为 4,那么就以 4 为基准对齐,如下图,所以所占空间大小为 20

(注意:计算机中,内存都是连续的,我这么画只是为了方便理解和计算,实际上都是在一条线上)

我们再看一个

struct s2
{
    int a; // 4
    char c; // 1
};

这里就需要用到我们的准则 3,如果最后一个大小不满足基准大小,也是要补齐的,所以所占大小为 8。

看到这里你应该已经了解了基本的对齐规则,接下来就要来一些进阶 -- 结构体嵌套

struct s3
{
    char a;
    s2 s;  // 这里的s2是上面的那个结构体
    char b;
};

结构体嵌套其实就需要把结构体拆开来看:

struct s3
{
    char a;
    s2 s{
      int a;
      char c;
    }
    char b;
};

根据对比,整个结构体中,最大的是int ,占 4 字节,所以就以 4 为基准对齐,但是,嵌套结构体所占空间是要和其他变量分开算的。

为了便于查看,我把没有用到的内存用 - 表示,可以看到嵌套结构体 s2 s 中的char c 与结构体s3 中的char b 并没有在一起,这是因为中间紫色 8 字节的空间,都是属于嵌套结构体s2 s的,char b不能占用,这里需要注意一下,所以最后该结构体所占大小为16

结构体内存对齐基本就是这些了,有一个很有意思题,如果感兴趣的话可以算一算:

struct s4     // 这里的 1 2 3 4...是编号
{  
      char a;  //1 
      struct   //2
      {  
           char g;  
           int h;  
      } ss;   
      char b;  //3
      char c;  //4
      char d;  //5 
      char e;  //6
      char f;  //7
}
  1. 当只有 1, 2, 3 时,占多大空间?

  2. 只有 1 ~ 6时,占多大空间?

  3. 1 ~ 7 ,一共占多大空间?