本帖最后由 香水城 于 2017-8-16 15:12 编辑
跳不出的 while 循环
问题:
该问题由某客户提出,发生在 STM32F103VBT6 器件上。据其工程师讲述:在为 STM32 调试软件过程中,遇到了一个怪现象:有如表(一)所示的一段程序中,KeyIn 是一个全局变量。当有按键按下时,键盘的中断服务程序会将对应的键值放入其中,当按键释放后,键盘中断服务程序则把 RESET 值放入该变量。行(1)的条件语句在主程序中,检测有无按键按下,并处理。行(2)则是等待按键释放。调试时,在行(1)和行(2)处各设置一个断点,然后全速运行程序。当程序停在行(1)时按下按键,继续全速运行程序。当程序停在行(2)时,放开按键,继续全速运行程序。这时,发现程序没有向下执行,而是依然回到并停留在行(2)处。打开观察窗口,观察变量KeyIn 的值,确认其值为 RESET。再次起动全速运行,程序依然回到行(2),而不是向下执行。
调研:
检查其软件工程,确认其所使用的工具链为 IAR Embedded Workbench IDE 5.3,工程设置中的优化等级为 High+Balanced。重复测试,确认现象如其所述。使用反汇编窗口检查编译器生成的指令,如图(一)所示:
从图(一)中得知,由语句 while(KeyIn != RESET) 生成的指令位于地址区间0x800032C-0x8000330,共有 3 条。其中,指令 LDR R1,[R0]负责将变量 KeyIn 的值装载到寄存器 R1 中,指令 CMP R1,#0 对该值进行检测,而跳转指令BNE.N ??main_1 则是实现循环。进一步观察,发现 3 条指令中,只有后两条参与了循环,而指令 LDR 只执行一次,不参与循环。于是可以得出这样的结论:如果在执行该循环语句的第一次循环时变量 KeyIn 的值非 0,那么装载到寄存器 R1 中的值就非 0,于是比较结果为“不相等”,从而进入下一次循环。由于从第二次开始,执行该循环时不更新寄存器 R1 的值,所以无论变量 KeyIn 的取值如何,比较的结果都是“不相等”,都会再次进入下一次循环。于是,一个无休止的循环诞生了。
将工程的优化选项更改为 High+Size,重新编译并观察反汇编结果,如图(二)所示:
这次由语句 while(KeyIn != RESET) 生成的指令变为 4 条,而且在每次循环中都重新向寄存器中装载变量 KeyIn 的值。从实际执行的效果上看,符合源代码所描述的逻辑。
将工程的优化选项改回到 High+Balanced 模式,并修改源程序,将变量 KeyIn 定义成volatile 型变量,如表(二)所示:
重新编译并观察反汇编的结果,如图(三)所示:
这种情况下,语句 while(KeyIn != RESET) 生成的指令为 3 条,但每次循环都重新装载变量 KeyIn 的值到寄存器。测试其执行效果,符合源代码所描述的逻辑。
结论:
通过以上的测试可以得知,该问题是由于编译器对 C 语言代码进行高度的优化时,对某
些语句解析错误造成的。
处理:
将变量 KeyIn 定义成 volatile 型变量,如表(二)所示。
建议:
既然这是一种由编译器的编译错误造成的问题,可否期待新版本的编译器为我们加以改正?非常不幸的是编译器真的很难做到这一点!更不幸的是几乎所有的编译器都有类似的问题!因为,在引用多个变量做循环处理时,如果对每个变量在每次循环中都重新装载,势必大大的降低代码的效率,根本谈不上优化。而编译器又无从分辨出哪些变量有可能发生改变,于是,只好认为只要在本循环中没有对其更改的变量就不会发生变化。所以,不能寄期望于编译器为我们解决这类问题。但这一问题还是要解决的,特别是基于多任务操作系的软件,其中有大量的变量由多个任务共享,如果发生此类问题将导致严重的错误。如何解决?有一个关键的切入点,就是:虽然编译器分辨不出哪些变量有可有发生改变,但是软件的设计者却是知道的。这样,完全可以通过某种方式告诉编译器,哪些变量有可以发生改变,或者通过一些手段迫使编译器对某个变量重新装载。将变量定义成 volatile 型变量,是告知编译器该变量有可以发生改变的一种方法。通常,外设的寄存器都定义成 volatile 型变量,因为这些变量的值有可能被硬件改变。定义成 volatile 型的变量在使用时往往不如普通的变量方便,因为要考虑对其读写的次序问题。比如表(三)所示的程序,编译器在对其编译的时候,会毫不客气的给出的警告:
可以人为的为这些变量设定读写次序,而避免产生相关的警告。如表(四)所示:
从表(四)中可以看出,定义这个次序不是一般的麻烦!那么,对于普通的变量,如何迫使编译器对其重新装载呢?我们可用函数来代替变量。比如,对表(一)所示的程序进行修改,得到表(五)所示的程序:
其中的 GetKeyIn()函数定义在另外一个 C 语言文件中。该函数只有一行语句,就是返回变量 KeyIn 的值。以 High+Balanced优化参数对表(五)的程序进行编译,并观察其反汇编代码,如图(四):
从图(四)中可以看到 while(GetKeyIn() != RESET) 语句被编译成了 3 条指令。其中,BL GetKeyIn 指令是用来调用函数 GetKeyIn()以取得变量 KeyIn 的值的,而且在每次循环中都对其调用,以重新装载变量 KeyIn 的值。在使用这种方法时,返回变量取值的函数一定要放在另外一个 C 语言文件中才有效。因为编译器进行编译的时候,是对工程中的 C 语言文件逐个进行的。由于被调用的函数不在当前的 C 语言文件中,所以编译器不知道该函数做了哪些事情,只好认为其返回值是有可能改变的,从而每次循环都对其调用,于是我们的目的就达到了。
对应的PDF:跳不出的while 循环
更多实战经验请看:【ST MCU实战经验汇总贴】
|