C语言结构体的字节对齐
什么是字节对齐
首先来看下面的程序:
#include <stdio.h>
typedef struct n1{
int a;
char b;
char c;
} N_stru1;
typedef struct n2{
char b;
int a;
char c;
} N_stru2;
int main() {
N_stru1 n1;
N_stru2 n2;
printf("%d\n", sizeof(n1));
printf("%d\n", sizeof(n2));
return 0;
}
两个输出的结果是什么?
如图:
明明结构体中的成员类型数量都是一样的,为什么会出现存储他们的结构体大小不一样的情况呢?
原因是这样的,下面是这两个结构体在内存中的存储结构:
n1:
地址 | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
内容 | a | - | - | - | b | c | / | / |
n2:
地址 | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x10 | 0x11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
内容 | b | / | / | / | a | - | - | - | c | / | / | / |
ps:在上面的表格中,“-”表示被使用,“/”表示被空置或者跳过
通过观察表格会发现:n2这个结构体中的字节有很多的空间被浪费了(因为有更多空置的内存),所以可以得出一个结论:
C语言的编译器在给结构体分配空间时按照结构体成员的声明顺序分配,并且其空间遵循内存边界要求最严格的成员的空间大小分配空间
如果你已经明白,那么可以跳过下面这个小板块的内容,如果还是没有,我讲配合上面的例子进行说明:
编译器对于结构体成员分配结论的详细解释
上面的结论有两点:
- 分配空间时按照结构体成员的声明顺序分配
- 遵循内存边界要求最严格的成员的空间大小分配空间
第一点,观察上表:
可以发现n1中的成员从地址最低位向最高位依次是:a、b、c
而n2中的成员从地址最低位向最高位依次是:b、a、c
这个顺序和我们在程序中对结构体成员的声明顺序是相同的。
第二点:
在这个例子中,发现n1和n2的大小分别是8和12,他们都是4的倍数。
为什么要说是4的倍数呢?
因为在结构体成员中,所需空间最大的变量就是int类型,也就是4个字节,所以在这个例子中,4就是内存边界要求最严格的成员变量。
那么比4小的1个字节是不能构成分配空间的单位的,所以说,编译器会在不足边界要求的成员的内存分配中给他们填充一些空的空间,以达到最低要求。
所以在上面的例子中:
n1:
地址 | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 |
---|---|---|---|---|---|---|---|---|
内容 | a | - | - | - | b | c | / | / |
a是int类型,占4个字节,系统直接分配,
b是char类型,占1个字节,不足4字节,系统需要填充三个字节
c也是char类型,占1个字节,不足4字节,但由于前面有空的字节,所以直接放在空字节中,不再分配空间
n2:
地址 | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x10 | 0x11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
内容 | b | / | / | / | a | - | - | - | c | / | / | / |
按顺序首先分配b,是char类型,不足4,填充3个字节
然后是a,满足4,不做处理直接分配
最后是c,不足4,前面字节已经被占用,因此再填充3个字节,分配
以上是对于结论的详细描述,不懂的可以在评论区提问。
那回到正题,什么是字节对齐呢?
下面是字节对齐的几个原则:
- 结构体的自身对齐值是其成员中自身对齐值最大的那个值,或者是指定对齐值(如果有的话)。
- 结构体的大小必须是其自身对齐值的整数倍,如果不足则补齐。
- 结构体的每个成员必须放在其自身对齐值的整数倍的地址上,如果不够则空出一些字节。
很多文章没有提到“对齐”这个概念,这里特别做一下解释:
“成员的空间分配 遵循内存边界要求最严格的成员的空间大小 分配空间”
其实就是上文中结论的后半部分
这里不再对64位和32位的系统进行比较,他们都遵循上述规则。
为什么要字节对齐
首先要明确的一点就是:CPU在内存中的取值是怎么样的。
操作系统位数与信息处理
显然,如果你的电脑是64位,那么CPU最大可以一次取64位(64bit换算成字节是8字节)宽的值(在内存中),然后对他们进行处理,这意味着:系统的操作位数越高,电脑运行速度会更快,处理多信息的能力就越强。
CPU读取数据的格式
CPU在访问内存时,通常是以一个字为单位,而一个字的长度就取决于CPU的位数。在64位操作系统中,一个字通常是8字节。那么一次性在内存中可以读取的字节大小就是8个字节,很舒服的是,在x86系统架构中,内存中一个内存块(也叫缓存行)的大小就是8个字节,这也恰恰是编译器在系统中开辟内存的单位,CPU一次性就可以读取这个内存块中的所有内容。
这个和字节对齐有什么关系呢?
现在来想一个问题:假设你正在给内存分配变量空间,现在已经分配到内存块的最后一个字节了(也就是说,这个内存块前面的内容已经被其他变量占用了,8字节的内存块只有1个字节是空闲的),此时你要分配一个4字节整形变量,那么你选择以下哪种分配方式?
- 从这个内存块最后剩下的1个字节开始分配一部分,然后再把剩下的一部分放在新的内存块中
- 直接丢弃空余的1字节,直接把这个变量放在新的内存块中。
仔细看过上面的文章的人应该都会选择第二个选项,因为这样可以减少CPU读取数据的次数。
字节对齐的意义
这就是字节对齐的意义,如果你字节对齐,你会发现,在C语言中,无论你怎么访问变量(除非这个变量大于内存块的大小),访问次数永远都是1次!!!不会出现CPU读取两次内存的情况!!!这样一来大大的提高了内存访问的速度
如何用利用系统字节对齐的特性提高自己的编程水平?
很简单,根据上面的例子就可以看出,当在结构体中声明变量的时候,我们应该尽量做到以下几点:
- 相同的数据类型放在一起
- 数据类型大的变量放在声明顺序之前
- 尽量的声明4的整数倍大小的空间(哪怕多声明的变量没有用)
对于最后一点,解释是这样的:
如果你声明了一个三个字节大小的空间(short是2个字节,加上1个char类型是三个字节),那么在字节对齐的影响下,最后一个字节就不能使用了,这块空间没有任何用,你为什么不多开辟一个变量,说不定未来能用上呢?
以上就是C语言结构体字节对齐的所有内容,创作不易!!
欢迎读者评论提问、点赞、关注!!!