C语言内存对齐
一.内存对齐的初步讲解
内存对齐可以用一句话来概括:
“数据项只能存储在地址是数据项大小的整数倍的内存位置上”
例如int类型占用4个字节,地址只能在0,4,8等位置上。
#include <stdio.h>
struct xx{
char b;
int a;
int c;
char d;
};
int main()
{
struct xx bb;
printf("&a = %p/n", &bb.a);
printf("&b = %p/n", &bb.b);
printf("&c = %p/n", &bb.c);
printf("&d = %p/n", &bb.d);
printf("sizeof(xx) = %d/n", sizeof(struct xx));
return 0;
}
执行结果如下:
&a = ffbff5ec
&b = ffbff5e8
&c = ffbff5f0
&d = ffbff5f4
sizeof(xx) = 16
会发现b与a之间空出了3个字节,也就是说在b之后的0xffbff5e9,0xffbff5ea,0xffbff5eb空了出来,a直接存储在了0xffbff5ec, 因为a的大小是4,只能存储在4个整数倍的位置上。打印xx的大小会发现,是16,有些人可能要问,b之后空出了3个字节,那也应该是13啊?其余的3个 呢?这个往后阅读本文会理解的更深入一点,这里简单说一下就是d后边的3个字节,也会浪费掉,也就是说,这3个字节也被这个结构体占用了.
二.操作系统的默认对齐系数
每 个操作系统都有自己的默认内存对齐系数,如果是新版本的操作系统,默认对齐系数一般都是8,因为操作系统定义的最大类型存储单元就是8个字节,例如 long long(为什么一定要这样,在第三节会讲解),不存在超过8个字节的类型(例如int是4,char是1,long在32位编译时是4,64位编译时是 8)。当操作系统的默认对齐系数与第一节所讲的内存对齐的理论产生冲突时,以操作系统的对齐系数为基准。
例如:
假设操作系统的默认对齐系数是4,那么对与long long这个类型的变量就不满足第一节所说的,也就是说long long这种结构,可以存储在被4整除的位置上,也可以存储在被8整除的位置上。
可以通过#pragma pack()语句修改操作系统的默认对齐系数,编写程序的时候不建议修改默认对齐系数,在第三节会讲解原因
#include <stdio.h>
#pragma pack(4)
struct xx{
char b;
long long a;
int c;
char d;
};
#pragma pack()
int main()
{
struct xx bb;
printf("&a = %p/n", &bb.a);
printf("&b = %p/n", &bb.b);
printf("&c = %p/n", &bb.c);
printf("&d = %p/n", &bb.d);
printf("sizeof(xx) = %d/n", sizeof(struct xx));
return 0;
}
打印结果为:
&a = ffbff5e4
&b = ffbff5e0
&c = ffbff5ec
&d = ffbff5f0
sizeof(xx) = 20
发现占用8个字节的a,存储在了不能被8整除的位置上,存储在了被4整除的位置上,采取了操作系统的默认对齐系数。
三.内存对齐产生的原因
内存对齐是操作系统为了快速访问内存而采取的一种策略,简单来说,就是为了放置变量的二次访问。操作系统在访问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。如果没有内存对齐时,为了读取一个变量是,会产生总线的二 次访问。
例如假设没有内存对齐,结构体xx的变量位置会出现如下情况:
struct xx{
char b; //0xffbff5e8
int a; //0xffbff5e9
int c; //0xffbff5ed
char d; //0xffbff5f1
};
操作系统先读取0xffbff5e8-0xffbff5ef的内存,然后在读取0xffbff5f0-0xffbff5f8的内存,为了获得值c,就需要将两组内存合并,进行整合,这样严重降低了内存的访问效率。(这就涉及到了老生常谈的问题,空间和效率哪个更重要?这里不做讨论)。
这样大家就能理解为什么结构体的第一个变量,不管类型如何,都是能被8整除的吧(因为访问内存是从8的整数倍开始的,为了增加读取的效率)!