原理简述

PC 实验

这边使用的环境为 intel i7, windows 7 ,对齐问题未影响操作结果,只对操作性能有所影响,windows 操作系统下即便不考虑对齐问题,依然可以编译运行成功。

1
2
3
4
5
6
7
unsigned char arr[50]={1, 2, 3, 164, 112, 157, 63, 7, 8, 9, 10};
float a;
int main(void)
{
a = *(float*)(&arr[3]);
printf("%f\n", a);
}

运行结果: 1.230000

MCU 下

MCU 选用 efm32hgxx 和 stm32f4xx 两款不同的芯片,对齐问题均会影响程序的实际运行。

错误代码:

1
2
3
4
5
6
7
8
9
10
unsigned char arr[50]={1, 2, 3, 164, 112, 157, 63, 7, 8, 9, 10};
float *Ptr;
float tmp;
int main(void)
{
float tmp = *(float *)(&arr[3]);
__nop();
}

错误结果:

1
2
3
4
5
6
7
8
9
仿真报异常,直接跳转到如下:
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
仿真器查看:
arr 地址: 0x20000000

分析可知,问题出在 &arr[3] 地址为 0x20000003,不为 4 的整数倍,不对齐,MCU 无法正确将该段地址解析成 float 等类型。解决这类问题,我们可以用 __align(x) 函数强制要求起始位置对齐,__align(4) 可以让变量对齐到 4 的整数倍地址。

ARM 下的对齐

使用 __align(num) 可以强制让数据或类型对齐,这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时就要用到此命令align(8)进行修饰限制,来保证数据对象是相应对齐。这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节对齐,但是不能让4字节的对象2字节对齐。align是存储类修改,他只修饰最高级类型对象,不能用于结构或者函数对象。

比如:__align(4) u8 mem1base[MEM1_MAX_SIZE];//保证分配的数组空间4字节对齐,同时保证数组首地址可被4整除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
如果需要将数组中任意位置开始的一段转换为某类型的话,可以有多种方法,可以通过中间量数组,强制转化。
// 方法1,使用中间量数组,强制四字节对齐
unsigned char arr[50]={1, 2, 3, 164, 112, 157, 63, 7, 8, 9, 10};
__align(4) unsigned char arrbuf[4]; // 注意对齐,否则仍会出错
float tmp;
int main(void)
{
for (int i=0; i<4; i++)
{
arrbuf[i] = arr[3+i];
}
tmp = *(float *)&arrbuf[0];
__nop();
}
// 方法2,使用中间量 float 类型数据(本身就会对齐四字节),对该数据的各个位强制赋值
typedef unsigned char u8;
u8 arr[50]={1, 2, 3, 164, 112, 157, 63, 7, 8, 9, 10};
float res;
int main(void)
{
for (int i=0; i<4; i++)
{
*((u8*)&res+i) = arr[3+i];
}
}
采用上述操作后,可以看到 arrbuf 地址变成了 0x20000034,再次取地址转换时,就可以成功了。

ARM 一字节对齐

packed packed是进行一字节对齐
1.不能对 packed 的对象进行对齐
2.所有对象的读写访问都进行非对齐访问
3.float及包含float的结构联合及未用packed的对象将不能字节对齐
4.
packed对局部整形变量无影响
5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定义为packed。
__packed int* p; //__packed int 则没有意义
6.对齐或非对齐读写访问带来问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//定义如下结构此时b的起始地址一定是不对齐的
//在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
//将下面变量定义成全局静态不在栈上
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
};
static char* p;
static struct STRUCT_TEST a;
void Main()
{
__packed int* q; //此时定义成 __packed 来修饰当前 q 指向为非对齐的数据地址下面的访问则可以
p = (char*)&a;
q = (int*)(p+1);
*q = 0x87654321;
/*
得到赋值的汇编指令很清楚
ldr r5,0x20001590 ; = #0x12345678
[0xe1a00005] mov r0,r5
[0xeb0000b0] bl __rt_uwrite4 //在此处调用一个写4byte的操作函数
[0xe5c10000] strb r0,[r1,#0] //函数进行4次strb操作然后返回保证了数据正确的访问
[0xe1a02420] mov r2,r0,lsr #8
[0xe5c12001] strb r2,[r1,#1]
[0xe1a02820] mov r2,r0,lsr #16
[0xe5c12002] strb r2,[r1,#2]
[0xe1a02c20] mov r2,r0,lsr #24
[0xe5c12003] strb r2,[r1,#3]
[0xe1a0f00e] mov pc,r14
*/
/*
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321
[0xe5812000] str r2,[r1,#0]
*/
//这样可以很清楚的看到非对齐访问是如何产生错误的
//以及如何消除非对齐访问带来问题
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
}

注意:对于上述 char 类型转换为 float,无法通过 packed 执行,因为 packed 无法对 float 操作,而如果是 32 位整型则可以通过 __packed 来字节对齐。

参考链接:
https://www.cnblogs.com/King-Gentleman/p/5940480.html