你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】跳不出的 while 循环

[复制链接]
STMCU小助手 发布时间:2022-3-4 20:13
问题:
8 U5 V) m  ?5 m, C该问题由某客户提出,发生在 STM32F103VBT6 器件上。据其工程师讲述:在为 STM32 调试软件过程中,遇到了一个怪现象:有如表(一)所示的一段程序中,KeyIn 是一个全局变量。当有按键按下时,键盘的中断服务程序会将对应的键值放入其中,当按键释放后,键盘中断服务程序则把 RESET 值放入该变量。行(1)的条件语句在主程序中,检测有无按键按下,并处理。行(2)则是等待按键释
1 F8 K! @3 Y3 h: e1 G% q" M) L. \放。调试时,在行(1)和行(2)处各设置一个断点,然后全速运行程序。当程序停在行(1)时按下按键,继续全速运行程序。当程序停在行(2)时,放开按键,继续全速运行程序。这时,发现程序没有向下执行,而是依然回到并停留在行(2)处。打开观察窗口,观察变量 KeyIn 的值,确认其值为RESET。再次起动全速运行,程序依然回到行(2),而不是向下执行。! Z8 B( ^+ S: _7 {+ U) C
1)M~8_56KKJ}96WT_U)AV67.png * a, b, B" C# t5 x2 t$ g' Q

: v9 M2 s/ @& s  c, G3 I. \- q+ n; M2 d" k$ p7 c( e
调研:" f* X& s" t& Q) l- m# E
检查其软件工程,确认其所使用的工具链为 IAR Embedded Workbench IDE 5.3,工程设置中的优化等级为 High+Balanced。重复测试,确认现象如其所述。使用反汇编窗口检查编译器生成的指令,如图(一)所示:; {/ L9 [& \) P$ E
' x6 @" y% R; O9 Q
5 |' g; S, @/ w$ L' ?

  g. C, v7 e# J+ k0 b$ R' l从 图 ( 一 ) 中 得 知 , 由 语 句 while(KeyIn != RESET) 生 成 的 指 令 位 于 地 址 区 间 0x800032C0x8000330,共有 3 条。中,指令 LDR R1,[R0]负责将变量 KeyIn 的值装载到寄存器 R1 中,指令 CMP R1,#0 对该值进行检测,而跳转指令 BNE.N ??main_1 则是实现循环。进一步观察,发现 3条指令中,只有后两条参与了循环,而指令 LDR 只执行一次,不参与循环。于是可以得出这样的结& T- H! y2 v" y6 e3 p- ?1 c$ P( b+ R
论:如果在执行该循环语句的第一次循环时变量 KeyIn 的值非 0,那么装载到寄存器 R1 中的值就非0,于是比较结果为“不相等”,从而进入下一次循环。由于从第二次开始,执行该循环时不更新寄存器 R1 的值,所以无论变量 KeyIn 的取值如何,比较的结果都是“不相等”,都会再次进入下一次循环。于是,一个无休止的循环诞生了。+ l4 j/ @' M9 Q+ ^' R
  c* A8 ~) H9 `3 T0 a' L$ S; z
将工程的优化选项更改为 High+Size,重新编译并观察反汇编结果,如图(二)所示:
2 v9 X3 x$ s& H1 K* d
; G+ x5 @: A' T! K P7AO51}_6[RK9TAQPK{)[N2.png
1 s! e3 X: j8 q. ~8 x# _' ~7 d/ U
: T- G- c. L) H6 {$ O这次由语句 while(KeyIn != RESET) 生成的指令变为 4 条,而且在每次循环中都重新向寄存器中装载变量 KeyIn 的值。从实际执行的效果上看,符合源代码所描述的逻辑。
9 I! J2 l- x- K4 Q) t: O' ~* }; Y9 W6 I5 k8 g$ F$ s8 l) T0 P& f$ s
将工程的优化选项改回到 High+Balanced 模式,并修改源程序,将变量 KeyIn 定义成 volatile 型变量,如表(二)所示:7 l: ]0 `7 ^3 Y# w

. Y/ p0 k- z) c; {) Z. J K6KGD`IH2X(2[BBR[TF}{[T.png 5 h; u( l1 H3 _. ]+ `& c

, {0 P0 o* q/ ]6 A8 |重新编译并观察反汇编的结果,如图(三)所示:( ^6 {3 ^; N# a. p" l4 |$ Y

# e' l7 n5 g% @* I5 i& E C_G[MZIPMJEU5FT$S~A5RRE.png 4 W8 u3 B! a% w8 W

" r; P, @9 ?+ N0 I+ T这种情况下,语句 while(KeyIn != RESET) 生成的指令为 3 条,但每次循环都重新装载变量 KeyIn的值到寄存器。测试其执行效果,符合源代码所描述的逻辑。
2 x$ Z) p% `$ u" ?$ q4 O
: l5 y6 S0 d1 K/ e) S8 M9 a; d  o# Q$ A) U3 B- C3 q- ]
结论:
) i+ }, P$ J$ a' g通过以上的测试可以得知,该问题是由于编译器对 C 语言代码进行高度的优化时,对某些语句解析错误造成的。5 I! N0 g! R( P& l7 B
( b: d% _, i* d" M
* x6 f' F7 ?0 @9 c: E  s7 |3 u
处理:) Q3 s. z- Z; q
将变量 KeyIn 定义成 volatile 型变量,如表(二)所示。
8 l  G& h& s8 G% x) J4 h* c1 D; c! i8 @2 D2 S1 R. K
建议:) R3 v* A' R/ n  F
既然这是一种由编译器的编译错误造成的问题,可否期待新版本的编译器为我们加以改正?非常不幸的是编译器真的很难做到这一点!更不幸的是几乎所有的编译器都有类似的问题!因为,在引用多个变量做循环处理时,如果对每个变量在每次循环中都重新装载,势必大大的降低代码的效率,根本谈不上优化。而编译器又无从分辨出哪些变量有可能发生改变,于是,只好认为只要在本循环中没有对其更改的变量就不会发生变化。所以,不能寄期望于编译器为我们解决这类问题。但这一问题还是要解决的,特别是基于多任务操作系的软件,其中有大量的变量由多个任务共享,如果发生此类问题将导致严重的错误。如何解决?有一个关键的切入点,就是:虽然编译器分辨不出哪些变量有可有发生改变,但是软件的设计者却是知道的。这样,完全可以通过某种方式告诉编译器,哪些变量有可以发生改变,或者通过一些手段迫使编译器对某个变量重新装载。将变量定义成 volatile 型变量,是告知编译器该变量有可以发生改变的一种方法。通常,外设的寄存器都定义成 volatile 型变量,因为这些变量的值有可能被硬件改变。定义成 volatile 型的变量在使用时往往不如普通的变量方便,因为要考虑对其读写的次序问题。比如表(三)所示的程序,编译器在对其编译的时候,会毫不客气的给出的警告:2 a8 m: j3 E  G
# R0 F/ S( u; S8 F! A
TKQAS_SS0DK9}0~P4}(9~XT.png ) ~& r* d; S8 I. |: t; a

- X2 H7 w# i, q9 m/ E4 v可以人为的为这些变量设定读写次序,而避免产生相关的警告。如表(四)所示:
7 Q& w! D( ?# L" D, y2 V+ N. e2 Z! R: A$ p7 `& n/ x) M; E& A1 n+ M

1 }. N/ k# O5 N% P QEW7SV96_E@P]4UX{4OE{0B.png # w& \9 D, j0 W  u) D

4 i- @' A% ?* g2 P从表(四)中可以看出,定义这个次序不是一般的麻烦!那么,对于普通的变量,如何迫使编译器对其重新装载呢?我们可以尝试用函数来代替变量。比如,对表(一)所示的程序进行修改,得到表( _0 U8 O3 Z1 A8 ~
(五)所示的程序:
  `( R( T4 }( f: M- c9 s; I6 Q* t
V7KD}QBLBZ~ZEDM17T_SPYE.png
  S) o2 q) C/ |; |; r5 R) G% G& R. j. I* ~% H6 Q. G5 ?- R2 W
其中的 GetKeyIn()函数定义在另外一个 C 语言文件中。该函数只有一行语句,就是返回变量 KeyIn的值。以 High+Balanced 优化参数对表(五)的程序进行编译,并观察其反汇编代码,如图(四):. s" p$ p: V+ K% t6 i/ T1 ~
( e+ ]8 f7 x. p; t! S

( d6 ~3 n! z" [- K/ ?9 N LSWHEEE)T]48U4HZ%CTW5W2.png
& l9 d+ h% _9 E9 k# u9 l, S) o* Z& e' n. T9 B2 X
从图(四)中可以看到 while(GetKeyIn() != RESET) 语句被编译成了 3 条指令。其中,BLGetKeyIn 指令是用来调用函数 GetKeyIn()以取得变量 KeyIn 的值的,而且在每次循环中都对其调用,以重新装载变量 KeyIn 的值。在使用这种方法时,返回变量取值的函数一定要放在另外一个 C语言文件中才有效。因为编译器进行编译的时候,是对工程中的 C 语言文件逐个进行的。由于被调用的函数不在当前的 C 语言文件中,所以编译器不知道该函数做了哪些事情,只好认为其返回值是有可能改变的,从而每次循环都对其调用,于是我们的目的就达到了。7 Z, t/ D" \  D2 c4 P! d
- _6 T8 V8 t9 W2 @( u/ U5 j
赞 收藏 评论0 发布时间:2022-3-4 20:13

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版