跳转至

C中的字节对齐

对齐跟数据在内存中的位置有关。

字长

在16位的CPU中,一个字刚好为两个字节;在32位CPU中,一个字是四个字节。若以字为单位,还有双字,四字


我们先来看看下面这个例子:

// pragma pack(n)
struct Node{
    int a;
    char b;
    double c;
};

上面这个例子中各个变量所占的字节分别是多少呢?我们逐个分析一下:

struct Node{
    int a;      // 0~3      int(4 byte)
    char b;     // 4~5      char(1 byte)
    double c;   // 6~13     double(8 byte)
};

所以答案就是:0~13一共14个字节

真的是这样吗?

答案是:肯定不是啊!


在计算机中,内存的存储和读取都是16的整数倍个字节,这是由CPU cache Line 决定的,一般是64个字节连续从读取(在Linux 32bit 操作系统情况下),所以不可能是14个字节存储的。

这就引出了本次要讲的东西:叫做字节对齐

字节对齐

字节对齐有以下原则:

  1. 变量的字节大小计算是从0开始的

  2. 变量所占的内存空间是变量所占字节大小的整数倍,比如int是4个字节,则所占内存就是8、12等4的倍数

  3. 变量所占空间位置要是变量的整数倍,比如int是4个字节,所以有关于int的变量要从4的整数倍开始计算内存空间

  4. 字节对齐有个概念叫做对齐模数,可以说是用来对齐内存的标准。

// 语法:
#pragma pack(n)  // n 一般是2^n次方,默认值是8
  1. 对于结构体而言,在对齐完各个变量之后,要再对整体进行以此拓展,让整个结构体内存对齐:

具体做法是:将整个结构体当中所占字节最大的变量与对齐模数进行比较,取小的数

让整个结构体拓展到最接近结构体字节数的上述取小值的倍数。

例如:一个结构体在初次对齐完之后是20个字节,对齐模数是8,所以应该将结构体的内存拓展到最接近20的8的倍数值,即24。


了解了字节对齐原则,返回看上面的例子,正确的解法应该是:

// pragma pack(n)
struct Node{
    int a;          // 0~3
    char b;         // 4~7
    double c;       // 8~15
};

/* 初次对齐为 0~15 ,所以是16个字节
 * 结构体中所占字节最大的变量类型为 double: 8
 * 对齐模数默认值为: 8
 * 取小值:(这里是一样大小)8
 * 16是8的整数倍,所以不用继续做拓展
 */

再来看看结构体嵌套结构体的情况:

struct A{
    int a;
    double b;
    char c;
};

struct B{
    char d;
    int e;
    A a;
};

对于结构体嵌套结构体的情况,其实就是将结构体变量在所嵌套的结构体中展开,然后用相同的方法解决:

具体解析如下:

struct A{
    int a;          // 0~7      有4个字节空洞
    double b;       // 8~15
    char c;         // 16~17
};
// 24 

struct B{
    char d;         // 0~3 
    int e;          // 4~7
    A a;            // 8~31
};
// 32