【问题背景】
O2编译选项广泛用于嵌入式编程中,可大幅降低CPU耗时。
但优化编译选项级别越高,编译工具链对源代码的指令优化与重排力度越大,最后生成的汇编指令越有可能与编程者预期的逻辑有偏差。
【问题现象】
目前在实际工作中发现O2下循环语句工作异常。实验代码段如下:
代码1
代码1实现的功能是:轮询地址为0xf0000000的数值,一旦数值为1则跳出循环继续进行“其他处理”。
在异构编程中这种写法经常被用于等待某一内存/寄存器的数值发生变化。
但实践发现,上述代码经过O2编译后,实际工作现象为:即使0xf0000000地址内数值为1,也无法跳出循环。这与编程预期严重不符。
【问题根因】
代码1经过O2优化后的汇编文件为:
代码2
从代码2可见,从地址0xf0000000中读取数值的动作只发生了一次,而在循环中未再读取,只是单纯地将第一次读取到的数值与立即数进行比对。故即使0xf0000000地址中的数值变化为了1,也无法跳出循环。
另外,通过去除优化选项作为对照组来确定问题根因为“启用了O2”,此时代码1的汇编文件为:
代码3
从代码3可见,在循环中每次都先读取0xf0000000地址中的数值后再与立即数进行比对,故在不启用O2的情况下,汇编指令的逻辑与编程者预期一致。
【解决方案】
通过上述分析可知问题根因为:启用了O2。但不使用O2是不现实的,尤其是对于CPU不强的平台来讲。
面对这种问题,可以在全部代码处于O2优化的情况下,通过使用volatile关键字来保护某些代码,避免编译器对其进行优化,即将代码1中的第3行改为:
unsigned int volatile *addr = 0xf0000000;
此时代码1的汇编文件为:
代码4
由代码4可见,在每次做比对之前,都会从0xf0000000中重新读取数值,符合预期逻辑。并且代码4比完全不使用优化编译选项的代码3更为精简,耗时更短。
【未解决问题】
目前在交叉编译工具链arm-seev100-linux-gnueabihf-和aarch64-none-linux-gnu-中均存在这个问题,但不确定使用gcc进行编译是否会存在这个问题,故暂不能完全确定是否和编译工具链有关。