内存对齐原因和规则

结构体中的元素在内存中并不是紧密排列的,原因在于CPU从内存取数据时,是一次取多个字节的,可以将内存看做多个抽屉,如每个抽屉中存放4字节,此时如果要取一个4字节的int值,如果该int值不在一个抽屉中而是一个抽屉里各存放一部分字节(即没有内存对齐),就需要读取两次内存内容(开两次抽屉)才能取到完整的int值,对性能有影响,甚至有些硬件架构下,不能访问任意地址的数据,只能从某些地址下才能访问特定类型的数据,否则会报错。

对齐规则1:结构体中的每个成员必须存放在该成员大小的整数倍偏移处。
对齐规则2:结构体大小必须是其最大元素的整倍数。

为了对齐而空出来的字节填0。

如以下结构体:

struct s1 {
    char c1;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c1存放在偏移量0处
    int i1;    // int大小为4字节,因此需要存放在4的整数倍处,因此c1和i1之间有3字节的0填充,此处的i1存放在偏移量4处
    char c2;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c2存放在偏移量8处
    int i2;    // int大小为4字节,因此需要存放在4的整数倍处,因此c2和i2之间有3字节的0填充,此处的i1存放在偏移量12处
    char c3;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c3存放在偏移量16处
    // c3后需要将结构体大小补齐到最大元素整数倍,未补齐时结构体大小为17字节,结构体中最大元素大小为4,因此结构体最后需要补3字节的0到20字节
};

以上结构体在内存中的大小是20字节而非简单的元素大小之和(11字节)。

结构体中的元素顺序不同,结构体在内存中的大小也不同:

struct s2 {
    char c1;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c1存放在偏移量0处
    char c2;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c2存放在偏移量1处
    char c3;    // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c3存放在偏移量2处
    int i1;    // int大小为4字节,因此需要存放在4的整数倍处,因此c3和i1之间有1字节的0填充,此处的i1存放在偏移量4处
    int i2;    // int大小为4字节,因此需要存放在4的整数倍处,此处的i1存放在偏移量8处
};

以上交换元素顺序后的结构体大小为12字节。

我们可以使用预编译命令#pragma pack(n)来将对齐模数设为n,对齐模数指的是:如果是int数据,则对齐模数是4,如果是double数据,则对齐模数是8。如果将结构s1的对齐模数设为2:

#pragma pack(2)

struct s3 {
    char c1;    // c1存放在偏移量0处
    int i1;    // int大小为4字节,对齐模数为2,i1会存放在地址2处,c1和i1之间只有1字节的0填充
    char c2;    // c2存放在偏移量6处
    int i2;    // int大小为4字节,对齐模数为2,i1会存放在地址8处,c2和i2之间只有1字节的0填充
    char c3;    // c3存放在偏移量12处
    // c3后需要将结构体大小补齐到对齐模数的整数倍处,未补齐时结构体大小为13字节,因此结构体最后需要补1字节的0到14字节
};

注意#pragma pack(n)的目的是让结构体更紧凑,如果n比默认应该空余的字节数要大,则按应该空余的字节数来计算,s3的结构体最后填补了1个字节而非3个字节就是这个原因,如:

#pragma pack(8)

struct s4 {
    char c;    // c存放在偏移量0处
    int i;    // i如果按pack的参数来计算,应该存在偏移量8处,会填充7个0字节,超出了默认填充3个0字节,因此会填充3个0字节
};

以上结构体的大小为8而非16。

你可能感兴趣的:(C/C++,硬件架构)