一、什么是对齐
现代计算机内存空间都是按照byte
划分的,理论上来说可以从任何地址开始对任意类型的变量进行访问。但在实际应用过程中,在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
二、作用
- 避免发生内存变量存取错误
不同的硬件平台在存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下的程序编程必须保证字节对齐。 - 避免存取效率上的损失,便于CPU的快速访问
有些平台如果不按照平台要求对存放的数据进行对齐,可能不会发生存取错误,但是会降低CPU从内存存取数据的效率。例如,有些平台每次读都是从偶地址开始,如果一个int
型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。 - 合理利用字节对齐存储数据可有效的节省内存空间
三、字节对齐的四个基本概念
-
数据类型自身的对齐值
(1)char
型数据,其自身对齐值为1 byte;
(2)short
型数据,其自身对齐值为2 byte;
(3)int
、float
型数据,其自身对齐值为4 byte;
(4)double
型数据,其自身对齐值为8 byte; -
结构体或者类的自身对齐值
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除
(2)结构体的每个成员相对于结构体首地址的偏移量都是成员大小的整数值,如有需要,编译器会在成员之间加上填充字节;
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节
字节对齐的细节和具体编译器实现相关,但一般而言,满足以上三个准则
- 可指定对齐值,语法如下所示(其中value为指定对齐值)
#pragma pack (value) //指定对齐值value
#pragma pack () //恢复自然对齐方式
或者
__attribute((aligned (value)))//指定对齐值value
__attribute__ ((packed))//恢复自然对齐方式
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值
四、对齐规则
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整。
五、对齐规则举例
1、结构体基本对齐
(1)创建两个结构体变量test1
和test2
。且两个结构体包含的结构体成员数量及类型相同。输出这两个结构体变量的大小。
代码示例如下:
struct str1
{
int a; //长度4byte
char b; //长度1byte
short c; //长度2byte
}test1;
struct str2
{
char b;
int a;
short c;
}test2;
void main(void)
{
printf("tes1大小为:%d byte\n ",sizeof(test1));
printf("tes2大小为:%d byte\n",sizeof(test2));
getchar();
}
运行结果如下:
结构体test1
和test2
中所包含的结构体类型分别为int
,char
,short
,理论来说,test1
和test2
的字节大小应该为4+1+2=7 byte,然而编译器输出的却与理论不符,且sizeof(tes1)
≠
\not=
=sizeof(tes2)
(2)通过程序输出两个结构体各成员变量的首地址,示例如下:
struct str1
{
int a; //长度4byte
char b; //长度1byte
short c; //长度2byte
}test1;
struct str2
{
char b;
int a;
short c;
}test2;
void main(void)
{
printf("结构体test1成员地址:\n");
printf("成员a首地址:%d\n",&(test1.a));
printf("成员b首地址:%d\n",&(test1.b));
printf("成员c首地址:%d\n",&(test1.c));
printf("结构体test2成员地址:\n");
printf("成员b首地址:%d\n",&(test2.b));
printf("成员a首地址:%d\n",&(test2.a));
printf("成员c首地址:%d\n",&(test2.c));
getchar();
}
运行结果如下所示:
通过以上程序运行结果,对成员地址进行分析如下:
2、指定对齐值
(1)同样是上面的例子,使用语法“#pragma pack (2)”
指定2字节对齐 (因为指定值为2 byte,结构体成员中长度最长为4 byte,则以指定值和最长长度中的最小值即2 byte作为有效对齐值对齐) ,查看此时结构体test1
和test2
所占字节长度,示例如下:
#pragma pack (2) //让所作用的结构成员对齐在2字节自然边界上
struct str1
{
int a; //长度4byte
char b; //长度1byte
short c; //长度2byte
}test1;
struct str2
{
char b;
int a;
short c;
}test2;
#pragma pack ()//取消缺省对齐,恢复自然对齐
void main(void)
{
printf("tes1大小为:%d byte\n",sizeof(test1));
printf("tes2大小为:%d byte\n",sizeof(test2));
getchar();
}
运行结果如下:
通过运行结果可知,通过指定对齐字节为2之后,结构体test1
和test2
所占字节长度都为8 byte。
(2)通过程序输出两个结构体各成员变量的首地址,运行结果如下:
通过运行结果,分析如下: