想必每位学习计算机的同学都应该知道,在C语言中有这么一个知识点--内存对齐,而面试题中关于这个知识点的题目也屡见不鲜,所以今天我就来浅显的谈一谈我对内存对齐的理解。
为什么要内存对齐
0.平台移植:各个硬件平台对对存储空间的处理有很大的不同,比如有些架构的CPU在访问一个没有进行对
齐的变量的时候会发生错误,这就需要我们保证内存对齐。
1.提高效率:因为处理器读写数据时,并不是以字节为单位,而是以块(2,4,8,16字节)为单位进行的。如
果不进行对齐,那么本来只需要进行一次的访问,可能需要好几次才能完成。假设内存的读取
粒度是4,cpu要读取一个int型(4字节)大小的内存。如果数据是从0字节开始,那么cpu只需
一次将0,1,2,3四个单元的内容读取出来即可;而如果你的数据跨越了这个边界,假设数据
是从2字节开始,那么这个时候CPU先访问一次内存,读取0-3字节进寄存器,再读取4-7字节进
寄存器,然后把0、6、7、8字节的数据删除掉,最后合并1-4字节的数据。可以看出,如果内存
没对齐,所进行的操作要复杂得多,所以效率当然会降低。
内存对齐的规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令
#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
1.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以3.结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任
后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,
比较小的那个进行。
解析:struct或union的数据成员,第一个数据放在offset为0的地方,以后每个成员的存储起始位置要从
该成员数据类型大小的整数倍开始。
2.结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按
照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的
那个进行。
解析:结构或联合体的总大小,也就是sizeof的结果,必须是其内部成员最大数据类型的整数倍,不
足的要补齐。
何效果。
/*本文例子都是在32位环境下适用的, 微软是严格按照对齐规则来分配内存的,但是linux下的编译器如gcc等,那就不一定了,要注意,gcc对任何2字节大小的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。*/
struct data1 { double; d; //8字节 int i; //4字节 char c1; //1字节 char c2[9]; //9字节 }
sizeof(struct data1)的值是24。
首先按照储存大小,double为8字节,int占4字节,根据规则1,8是4的倍数,满足规则,char占
1个字节,8是1的倍数,满足规则,最后一个c2[9]占9个字节。8+4+1+9=22,而根据规则2,该结
构体显然是以8字节对齐的,而22不是8的倍数,所以需要进行对齐,对齐后的值即为24。
union data1 { double; d; //8字节 int i; //4字节 char c1; //1字节 char c2[9]; //9字节 };
sizeof(union data1)的值是16。这个程序与上一个程序的变量完全一样,只是一个是结构体,一个是共用体,可sizeof的结果却相差甚远,这是因为,共用体是共用一块内存,而sizeof的结果则是共用体中占内存最大的变量的所占字节数。该共用体最大基本类型为double,它占8字节,所以此共用体以8来对齐。字符数组c2占9个字节,那么整个共用体应该占9个字节,但按照规则,实际应该分配给它的内存为8的倍数即16字节。
struct data2 { char c1; //1字节 char c2[3]; //3字节 };
sizeof(struct data2)的值为4。
该结构体占内存空间最大的基本数据类型为char,长度为1,所以该结构体按1来对齐,c2占3字节,是1的倍数,满足规则,而1+3=4也是1的倍数,所以该结构体的分配的内存空间为4字节。
union data2 { char c1; //1字节 char c2[3]; //3字节 };
sizeof(union data2)的值为3。同上面的struct data2类似,以1来对齐,分配的存储空间为3。
struct data3 { char c1; //1字节 double d; //8字节 char c2; //1字节 };
sizeof(struct data3)的值为24。
这个结构体显然是以8字节对齐的。在分配空间时,首先c1被分配了1个字节,而接下来的d为8字节,根据规则1,它存储的起始位置要从8的整数倍开始,即要从8开始分配,所以前面c1的0字节后,要被补从1-7上7个字节,最后给c3分配了一个字节,8+8+1=17,但根据规则2需要进行对齐,对齐为8的倍数,所以最后的结果是24。
struct data4 { char c1; //1字节 char c2; //1字节 double d; //8字节 };
sizeof(struct data4)的值为16。这个结构体也是以8字节对齐的,但编译器编译程序时,给c1,c2分配存储空间没必要,各自给他们分配8字节,两个一共8字节就可以了,给d自然也分配8字节,因此sizeof的值为16。也可以这么理解,c1占1个字节,1是8的整数倍,满足规律,c2占1个字节,1是8的倍数,满足规律,d占8个字节,根据规律1,它存储的起始位置要从8的整数倍开始,即要从8开始分配,所以前面只要补6个字节就可以了。所以一共分配16个字节。
补充一点:如果一个结构体里有含有某些结构体,则该结构体的对齐系数是其内部最大的基本数据类型(包括被包含的结构体中的数据类型)。
struct inner { char c1; //1字节 double d; //8字节 char c2; //1字节 }; struct data4 { struct inner t1; //24字节 int i; //4字节 char c; //1字节 };
sizeof(struct data4)的值为32。struct data4是以8个字节对齐的,因为它的成员中最长的数据类型是double 8字节,t1占24个字节,i占4个字节,是8的倍数,满足规则1,c占1个字节,24+4+1=29不是8的倍数,所以内存补齐为32个字节。
struct inner { char c1; //1字节 double d; //8字节 char c2; //1字节 }; union data3 { struct inner t1; //24字节 int i; //4字节 char c; //1字节 } ;
sizeof(struct data3)的值为24。
同上一个例子一样,union data3是以8字节对齐的,它的存储长度取决与t1,因为它所占的内存空间最大,而t1长度为24,是8的倍数,因此sizeof(union data3)的值为24。
struct data { int a; //4字节 long b; //4字节 double c; //8字节 float d; //4字节 char e[3]; //3字节 short f; //2字节 }DATA;
这个结构体所占的内存是多少呢?首先。这个结构体是以8字节对齐的,在分配存储空间时,a分配4个字节,b分配4个字节,c分配8个字节,d分配4个字节,e分配3个字节,f占2个字节,可前面a,b,c占了16个字节,d,e占了7个字节,而根据规则1,f要从2的整数倍开始存储,所以给前面的e补齐为4个字节,这样,该结构体此时共占4+4+8+4+4+2=26个字节,再根据规则2,将该结构体的内存补齐为8的倍数,即变成32个字节。