原理简述

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 的整数倍地址。

而如果需要将数组中任意位置开始的一段转换为某类型的话,往往需要通过中间量数组才行,另外该中间量必须也要对齐,这些操作并不是特别方便,但目前还没有找到更有效的方法。

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
// 方法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,再次取地址转换时,就可以成功了。