Linux下字节对齐
1、什么是字节对齐?
数据在内存中存放的时候按照一定空间的规则来排列,而不是按顺序的一有空位就放进去。例如在32位系统下会按照4字节对齐,64位系统则为8字节对齐。如下图:
在开始的时候放了一个char
型的字符C
,后面接着一个int
类型的数据,正常来讲,这个int
类型的数据应该会紧跟在C
的后面,如下图,但是他并没有,而是跳过了3个字节,存放到了第4个字节之后,这就是字节对齐。
为什么要字节对齐呢?用32位的机器来讲,CPU不是一个字节一个字节来读取的,而是一次读取4个字节。那上面的图片来讲,如果不是字节对齐的话,CPU想要读取这个int
类型的数据,那么就要读取两次,而字节对齐的话只需要读取一次就够了!大大提高了CPU读写的效率!其实就是用空间换取时间!
2、字节是怎么对齐的呢?
通常来讲32位机器都是按4字节对齐,64位机器按照8字节对齐,字节对齐在结构体中表现得较为明显。例如下面的代码:
#include <stdio.h>
struct student
{
char a;
int b;
double c;
char d;
};
int main(int argc, char const *argv[])
{
printf("%lu\n", sizeof(struct student));
return 0;
}
编译运行上面的代码获得如下的结果:
$ gcc b.c -o b
$ ./b
24
编译环境是(gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
)。
可以看到,输出的这个结构体所占用的字节大小为24个字节,但是在我们的认知中,它应该是1 + 4 + 8 + 1 = 14个字节,这和我们预想的结果却有很大出入,说明它确实发生了字节对齐!
3、字节对齐的原理
我们可以使用结构体里面每个成员相对于结构体基地址的偏移量来分析。
struct student
{
char a; // 大小为1个字节,偏移量为0
int b; // 大小为4个字节,偏移量为1,但是1不是本身字长的整数倍,因此偏移量为4,占用4个字节
double c; // 大小为8个字节,由于b已经占用了第4到7的位置,所以偏移量 为8,刚好是本身字长的整数倍,占用8个字节
char d; // 大小为1个字节,由于整个结构体中最长的项是8个字节,因此d后面7个字节补0
};
根据上面的分析,a和b共占用了8个字节,c本身就需要占用8个,d只占用1个,但是也要占用整个结构体最长的double
类型的字长,就是8个,后面7个字节补0。所以可以得到8 + 8 + 8 = 24字节。
我们可以使用下面的程序来验证一下:
#include <stdio.h>
struct student
{
char a;
int b;
char e; // 在原来的b和c之间插入一个char e;
double c;
char d;
};
int main(int argc, char const *argv[])
{
printf("%lu\n", sizeof(struct student));
struct student stu;
printf("stu = %p\n", &stu); // 打印出结构体stu的基地址
printf("a = %p\n", &stu.a); // 打印出结构体成员a的地址
printf("b = %p\n", &stu.b); // 打印出结构体成员b的地址
printf("e = %p\n", &stu.e); // 打印出结构体成员e的地址
printf("c = %p\n", &stu.c); // 打印出结构体成员c的地址
printf("d = %p\n", &stu.d); // 打印出结构体成员d的地址
return 0;
}
我们先来分析一下这个代码:
- 首先,a和原来一样,占用1个字节,是首元素,偏移量为0;
- 然后b是int,4个字节,前面有一个a,因此偏移量为1,但是由于1不是自身字长4的整数倍,因此偏移量变为4,占用4-7的字节;
- 接下来是char型的e,偏移量为8,本身1个字节;
- 接下来是关键,下面的c是
double
型,64位机器里占用8个字节,c的偏移量为9,但是9不是8的整数倍,所以偏移量变为16,占用16-23的字节; - 最后和原来一样,d的偏移量为24,占用一个字节,但是由于最长的是8字节,因此后面的7个字节也需要占用,并且补0;
- 所以,最后得到的结果应该为8 + 8 + 8 + 8 = 32个字节。
我们运行来试验一下:
$ gcc b.c -o b
$ ./b
32
stu = 0x7fff768b3ff0
a = 0x7fff768b3ff0
b = 0x7fff768b3ff4
e = 0x7fff768b3ff8
c = 0x7fff768b4000
d = 0x7fff768b4008
结果和我们想象的一样,结构体占用32个字节。对结果一个一个分析:
- a的地址和结构体的基地址一样,偏移量为0;
- b的地址为
a+4
,偏移量为4,a~b中间的3个字节补0; - 因为b本身占用4个字节,所以e的实际地址是
b+4
,相对于基地址偏移量为8; - 接下来c的地址为
e+8
,相对于基地址偏移量为16; - d占用1个字节,因为
double
本身占用8个字节,所以地址为c+8
,,相对于基地址偏移量为24; d-a
的结果为24,结构体占的总长为32,因此后面还有8个字节,其中一个字节存放的是d。
可以用下图来表示:
4、偏移的一般规则
-
结构体的基地址是可以整除该结构体里字长最长的成员字长的;
-
首元素的偏移量为0;
-
其他成员的偏移量为自己本身字长的整数倍;
-
最后若干个元素按照上面的规则也没有占用8个字节,例如上面的结构体
student
里有一个double
为8个字节,所以最后的d即使是一个只占用1个字节的char
,也依然分配了8个字节。第4点值得注意的是,如果在d后面再加一个int型,结果仍然是32。至于为什么,可以自己思考一下。
感谢!