(2)产生hard fault的情况。
主循环的起始部分汇编代码如下,需要在Time_Display()后的寄存器值和回退地址都弹出。
[cpp] view plaincopy
- <span style="font-size:13px;"> 196: void Time_Show(void)
- 0x08000D90 BD1F POP {r0-r4,pc}
- 0x08000D94 517F STR r7,[r7,r5]</span>
刚进入Time_Display()时的汇编代码如下,将R0-R4,及LR都压入栈中。
[cpp] view plaincopy
- 165: void Time_Display(uint32_t TimeVar)
- 0x08000D40 E8BD4010 POP {r4,lr}
- 0x08000D44 F7FFBEEA B.W RTC_WaitForLastTask (0x08000B1C)
- 166: {
- 167: uint32_t THH = 0, TMM = 0, TSS = 0;
- 168: char buf[10];
- 169: /* Reset RTC Counter when Time is 23:59:59 */
- 0x08000D48 B51F PUSH {r0-r4,lr}
- 0x08000D4A 4604 MOV r4,r0
此时STM32芯片寄存器和内存的情况如下图所示。
此时buf的地址为0x200003ec,即R1的起始位置。变量和寄存器值的覆盖关系,或许是编译器检测到R1~R3的值在出栈后将不会被使用,而对内存进行的优化。此时内存中没有其他局部变量的位置,是因为在改动了代码的情况下,编译器判断为,只需在寄存器里就可以完成计算操作,因此改变了函数的汇编代码,没有占用内存空间。buf的赋值是按从低地址到高地址的顺序进行的。从内存的分配图中可以看出,如果buf越界,数组元素超过12个,就将影响到R4的内容。而如1中所述,R4的内容是Time_Display()退出后,需要读取的内存地址。如果经sprintf()后,buf内有15个字符,加上0x00,共16个字符,正好完全覆盖R4,且R4的最高位为0x00,显然是一个非法的内存空间,因此将进入hard fault。如果buf内的字符数落在(12,16)区间内,R4的地址合法(仍为0x20开头),不会进入hard fault,但地址已被修改,错误的内存空间中数值未知,程序跑飞。这些分析与实际测试结果是一致的。
问题得到了解释,也不知花了一天时间分析这些值不值。出错与否,除了程序本身的正确以外,编译器将C翻译成汇编的发挥程度也是很大的决定因素。想避免这些头疼的问题,结论就一句话:数组不要越界。 |