1、什么是字节对齐
现代计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。
2、字节对齐的原因和作用
不同硬件平台对存储空间的处理上存在很大的不同。某些平台对特定类型的数据只能从特定地址开始存取,而不允许其在内存中任意存放。例如Motorola 68000 处理器不允许16位的字存放在奇地址,否则会触发异常,因此在这种架构下编程必须保证字节对齐。
但最常见的情况是,如果不按照平台要求对数据存放进行对齐,会带来存取效率上的损失。比如32位的Intel处理器通过总线访问(包括读和写)内存数据。每个总线周期从偶地址开始访问32位内存数据,内存数据以字节为单位存放。如果一个32位的数据没有存放在4字节整除的内存地址处,那么处理器就需要2个总线周期对其进行访问,显然访问效率下降很多。
因此,通过合理的内存对齐可以提高访问效率。为使CPU能够对数据进行快速访问,数据的起始地址应具有“对齐”特性。比如4字节数据的起始地址应位于4字节边界上,即起始地址能够被4整除。
此外,合理利用字节对齐还可以有效地节省存储空间。但要注意,在32位机中使用1字节或2字节对齐,反而会降低变量访问速度。因此需要考虑处理器类型。还应考虑编译器的类型。在VC/C++和GNU GCC中都是默认是4字节对齐。
3、字节对齐原则
* 1、结构体变量的首地址能被对齐数整除
* 2、每个成员相对首地址的offset都是对齐数的整数倍
* 3、结构体总的大小为对齐数的整数倍
* 4、不同的编译器对齐数不一样,gcc的对齐数是4,不是max_width
4、设置对齐方式
主要是更改C编译器的缺省字节对齐方式。
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack(n):C编译器将按照n个字节对齐;
使用伪指令#pragma pack(): 取消自定义字节对齐方式。
另外,还有如下的一种方式(GCC特有语法):
__attribute__((aligned (n))): 让所作用的结构成员对齐在n字节自然边界上。如果结构体中有成员的长度大于n,则按照最大成员的长度来对齐。
__attribute__ ((packed)): 取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
4、代码示例
/**
* 32位OS默认按4字节对齐
*/
struct A{
int a;
char b;
short c;
}AT;
struct B{
char b;
int a;
short c;
}BT;
#pragma pack(2) /* 按2字节对齐 */
struct C{
char b;
int a;
short c;
}CT;
#pragma pack()
#pragma pack(1) /* 按1字节对齐 ,则大小为类型长度总和*/
struct D{
char b;
int a;
short c;
}DT;
#pragma pack()
#define GNUC_PACKED __attribute__((aligned(2))) /* 按2字节对齐 */
struct KT{
char b;
int a;
short c;
}GNUC_PACKED;
struct Tone{
int m1;
char m2;
float m3;
union Tun{
char u1[5];
int u2[2];
}UT;
double m4;
}ToneT;
void testStSize()
{
printf("%d,%d\n",sizeof(AT),sizeof(BT));//8,12
printf("%d,%d\n",sizeof(CT),sizeof(DT));//8,7
printf("%d\n",sizeof(ToneT));//32
}