内存对齐的作用:
1、不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取特定类型的数据,否则抛出硬件异常(可参考【培训】考试试题1)。
2、数据结构尤其是栈,应该尽可能的在自然边界上对齐,可以让处理器尽可能的只做一次访问。
char、int、double等基础变量类型的自然对齐原则
变量地址能够被类型大小整除
数组的自然对齐原则
按数组元素的类型处理
struct自然对齐的3个原则
1、结构体变量的起始地址能够被其最宽成员类型大小整除,第一个成员地址一定等于起始地址;
2、结构体每个成员相对于起始地址的偏移能够被其自身类型大小整除,如果不能则在前一个成员后面补充字节;
3、结构体总体大小能够被最宽成员类型大小整除,如不能则在后面补充字节;
注意:如果程序中有#pragma pack(n)预编译指令,对于第1和第3原则,取n和结构体内最宽成员类型大小中的最小值作为对齐标准,用"对齐标准"替换原则中的"最宽成员类型大小"。对于第2原则,取n和每个成员自身类型大小中的最小值作为对齐标准,用"对齐标准"替换原则中的"自身类型大小"。
举例:
struct S
{
char c;
int i;
};
sizeof(struct S)为8
struct S
{
double d;
char c;
int i;
short s;
};
sizeof(struct S)为24
#pragma pack(1)
struct S
{
char c;
int i;
};
#pragma pack()
sizeof(struct S)为5
#pragma pack(4)
struct S
{
char c[3];
short s;
};
#pragma pack()
sizeof(struct S)为6,char c[3]虽然整体大小占3直接,但是每个成员还是1字节,结构体里最大成员单位是2字节,整个结构体对齐到2的整数倍。如果c是一个3字节大小结构体,结果不变,结构体大小不属于成员类型。
结构体字节对齐意义:
将一个结构体数据拷贝到一段字节缓存中,用偏移去取数据,如果结构体字节不对齐就会出问题,可以用#pragma pack(1)去修饰。在使用这种方法的时候,对于结构体里uint8_t可以直接访问,但是对于uint16_t及以上,必须用单字节拆解拼合的方式去读写,不能直接使用,寄存器地址之类的必须对齐使用。补充:如果编译优化等级不是-O0,一般的编译器会优化这种非对齐结构体,实际汇编码是单字节访问,同时对于奇地址不能强转,强转的话优化效果会消失。总结就是非对齐结构体的变量可以直接使用,但是不能把一个奇地址赋值或强转成一个偶地址指针,再使用这个指针处理变量。
union自然对齐的3个原则
1、联合体变量的起始地址能够被其最宽成员类型大小整除;
2、联合体每个成员相对于起始地址的偏移都是0;
3、联合体总体大小能够被最宽成员类型大小整除,如不能则在后面补充字节;
union U
{
double d;
int i[5];
char c;
};
sizeof(union U)为24
堆栈内存对齐( 全局或静态数据直接按自然对齐即可****)****
- 堆栈区的起始地址由编译器强制对齐到8字节(启动或者链接文件中配置),(UCOS上线程栈没有对齐到8字节就会出现浮点数打印异常);
- AAPCS(Procedure Call Standard for the Arm Architecture)规范要求栈在任何时候都保持4字节对齐,在函数调用入口点保持8字节对齐。这意味着在调用函数时,栈指针必须是8字节对齐的。但是如果某个空函数没有用栈空间,编译器会优化成4字节对齐,减少压栈(函数调用等于压栈,函数返回等于出栈,压栈==入栈,弹栈==出栈)。
- 中断入口函数的栈顶地址可以通过SCB(有的是NVIC)中的STKALIGN置位来对齐到8字节,如果压栈时减4字节,出栈时会自动再加4字节(栈区是由高到低,向下生长)。
- 局部变量的对齐原则为自然对齐,(AC5下所有局部变量地址强制对齐到4字节);
