由于某些硬件平台不能任意访问地址数据,只能在某些地址处取某些特定类型的数据;并且处理器访问未对齐的内存时,需要多次读取并对多余数据进行剔除,相较于对齐内存访问,耗费了更多的时间,降低了数据访问效率,因此需要内存对齐。
一、内存字节对齐的规则
1.数据类型自然边界对齐
char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节,long型数据为4字节(32位编译器)或8字节(64位编译器),void*型数据为4字节(32位编译器)或8字节(64位编译器)。
2.结构体、类的自身对齐
为结构体分配内存时,分配的内存大小至少是各个字段的长度和。通常,分配的结构体的长度会大于结构体各个字段的长度和,因为结构体需要对齐,即结构体各字段之间需要填充。
缺省情况下,编译器为结构体的每个成员按其自然边界对齐方式分配空间,并按照每个成员被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。结构体整体的默认字节对齐值是结构体中所有成员中对齐参数最大值,结构体长度的计算必须取所用过的所有对齐参数的整数倍。
结构体中每个成员的首地址是成员自身自然边界对齐值的整数倍,如果成员自身大小大于指定对齐字节,以指定对齐字节整数倍为基准对齐;
结构体整体对齐方式取决于结构体中所有成员的自然边界对齐值最大值的整数倍,如果最大成员大小超过指定对齐字节,以指定对齐字节整数倍为基准对齐(也叫补齐,目的是多个结构体连续存储时也满足对齐要求)。
3.编译器指定对齐
内存字节对齐是GCC编译器对C语言进行的扩展。在缺省情况下,C编译器为每一个变量或是数据单元按其自然边界对齐条件分配空间。
(1)GCC编译器两种内存对齐的方法
属性设置方式(推荐)
__attribute__ ((aligned(n))); //让所作用的结构体或者类或者联合或者一个类型的变量(对象)分配地址空间时的地址在编译过程中按n字节对齐
__attribute__ ((packed)); //取消结构在编译过程中的优化对齐
如果__attribute__((aligned(n)))作用于一个类型,那么该类型的变量在分配地址空间时,其存放的地址一定按照n字节对齐(n必须是2的幂次方);如果类型中的成员的自然边界对齐值大于n,则按照机器字长(32位或者64位)对齐。类型占用的空间,即大小,需要是n的整数倍,以保证在申请连续存储空间的时候,每一个元素的地址也是按照n字节对齐。
32位处理器指定4字节对齐方式实例如下:
struct test{char a[18];double b;char c;int d;short e;
}__attribute__((aligned(4)));//实际有效指定对齐值为4字节(机器字长)sizeof(struct test);//40字节
第一个成员(char a[18]):假设放在内存开始地址为0的位置,占用内存地址范围为0~18。
第二个成员(double b):double类型占8字节,大于指定对齐字节(4字节),因此以4字节对齐为基准。由于第一个成员结束地址为18,不是4的整数倍,需要再加2个字节从地址20开始。
第三个成员(char c):char类型占1字节,可以直接从第二个成员结束地址28开始。
第四个成员(int d):int类型占4字节,地址29不是4的整数倍,需要加3个字节,从地址32开始。
第五个成员(short e):short类型占2字节,地址36刚好是2的整数倍,可以直接放在当前地址。
最后是对整个结构体补齐,该结构体中最大字节为8字节(double类型),大于指定对齐字节,所以还是以4字节补齐为基准。整个结构体结束地址为38,地址38不是4的整数倍,需要额外加2个字节填充,即整个结构体的大小为40字节。具体对齐结构如下图所示。
伪指令方式(最多只支持8字节对齐,不建议使用)
#pragma pack(n) //n取值可以为1、2、4、8,在编译过程中按照n个字节对齐
#pragma pack() //取消指定对齐,按照编译器的优化对齐方式对齐
32位处理器指定4字节对齐方式实例如下:
#pragma pack(4)
struct test{char a;int b;short c;char *p;double d;
};
#pragma pack()sizeof(struct test);//24字节
(2)其他编译器内存对齐方式
#if defined (__CC_ARM) /*!< ARM Compiler */ //MDK__align(4) uint16_t data[40];//存储类修饰符,只修饰最高级类型对象,不能用于结构或者函数对象//__packed是1字节对齐,不使用__packed的话系统以默认方式对齐#elif defined ( __ICCARM__ ) /*!< IAR Compiler */ #pragma data_alignment=4 uint16_t data[40];#elif defined (__GNUC__) /*!< GNU Compiler */ __attribute__ ((aligned (4))) uint16_t data[40]; #elif defined (__TASKING__) /*!< TASKING Compiler */ __align(4) uint16_t data[40];#endif /* __CC_ARM */
二、检查内存地址是否对齐(4字节为例)
4字节对齐的地址是4(100b)字节的整数倍,也就是说地址的二进制表示形式以00结尾,因此可以通过以下方式对4字节对齐地址进行测试:
if((address & 0x3) == 0)
{//The address is 4-byte aligned here
}if(address & 0x3)
{//The address is not 4-byte aligned here
}