关于8位指针强制转16位隐晦问题
最近遇到一关于8位指针转16位指针访问的隐晦问题,函数关键部分如下。
void fun(uint8_t *buf)
{
uint16_t data = 0;
data = *((uint16_t *)&buf[0]);
/* todo */
}
该部分代码在MCU、RTOS、Linux奇Windows下长期使用过,未暴露问题。后面出现状况是移植到STM32F0上执行时,直接导致MCU复位。
原因分析:
【1】内存对齐问题,大部分情况下传入内存地址为偶数,未暴露问题;暴露问题的时候,传入内存地址为奇数;
【2】摘自网络某描述——大部分16位和32位的CPU不允许将字或者长字存储到内存中的任意地址。 比如Motorola 68000不允许将16位的字存储到奇数地址中, 将一个16位的字写到奇数地址将引发异常;
【3】摘自一些书籍的描述——奇数地址强制转为16位(字)指针访问,会移至下一地址访问,如当前地址为0x03,期望问地址是0x03、0x04,但实际可能访问的是0x04、0x05。
验证:
写一段测试代码,编译器分配的静态内存一般都是从4字节对齐(偶数)开始分配,指定为奇数地址开始,在IAR下编译器报错;但可以从分配的内存偏移地址(奇数地址)开始访问。
#include <stdio.h>
#include <stdint.h>
int main(int argc, char** argv)
{
static uint8_t buf[5]= {0x12,0x34,0x56,0x78,0x90};
uint16_t temp = 0;
printf("buf[0] addr:%x\r\n", (uint32_t)&buf[0]);
printf("buf[1] addr:%x\r\n", (uint32_t)&buf[1]);
temp = *((uint16_t *)&buf[0]); /* 偶数地址 */
printf("temp = %x\r\n", temp);
temp = *((uint16_t *)&buf[1]); /* 奇数地址 */
printf("temp = %x\r\n", temp);
return 0;
}
【1】在Ubuntu下可以正确执行,且结果符合期望值,不论奇偶地址;
【2】在STM32F0下运行上面程序,执行“temp = *((uint16_t *)&data[1])”语句后,MCU复位。
结论:
【1】不同平台对于奇数地址强制转换时,情况不一样;
【2】提高代码可靠性和重用性,慎用甚至不用指针(地址)强制转换,直接使用值转换,但要注意大小端问题,如前面代码改成这样则不会引发问题;
data = (buf[1] << 8) | buf[0]; /* 小端模式 */