|
发表于 2012-9-26 15:19:53
|
显示全部楼层
本帖最后由 zhzhchang 于 2012-9-26 15:33 编辑
好个一丝不苟的编译器
这是个十分奇葩的问题,碰巧被我遇到了,我承认是我代码写的不够规范,但正是这个不规范的代码,才得以发现这个奇葩的事件。实在忍不住用了两个奇葩来形容。把过程简化一下,如下所述:
假如你的工程至少有两个.c文件,其中一个为timer.c,里面有个定时器中断程序,每10ms中断一次,定义一个变量来统计定时器中断次数:
unsigned int unIdleCount;还有一个timer.h文件,里面是一些timer.c模块的封装,其中变量unIdleCount就被封装在里面:
extern unsigned int unIdleCount;
在main.c函数中,包含timer.h文件,并利用定时器变量unIdleCount来精确延时2秒,代码如下:
unIdleCount=0;
while(unIdleCount!=200); //延时2S钟
keil MDK V5.54下编译,默认优化级别,编译后下载到硬件平台。你会发现,代码在while(unIdleCount!=200);处陷入了死循环。反汇编,代码如下:
122: unIdleCount=0;
123:
0x00002E10 E59F11D4 LDR R1,[PC,#0x01D4]
0x00002E14 E3A05000 MOV R5,#key1(0x00000000)
0x00002E18 E1A00005 MOV R0,R5
0x00002E1C E5815000 STR R5,[R1]
124: while(unIdleCount!=200); //延时2S钟
125:
0x00002E20 E35000C8 CMP R0,#0x000000C8
0x00002E24 1AFFFFFD BNE 0x00002E20
重点看最后两句汇编代码,寄存器R0是当前变量unIdleCount的值,汇编指令CMP为比较指令,如果R0中的内容与0xC8不等,则循环。但是这里并没有更新寄存器R0的代码,也就是说变量unIdleCount的值虽然在变化,但跟0xC8一直比较的却是内容不变的R0。因为之前变量unIdleCount被清零,所以R0的内容也是0,永远不等于0xC8,永远不会跳出循环。
看到这里,也许你已经笑翻了:你这个小白,这很明显是没用volatile修饰变量unIdleCount造成的!!!不错,比起从RAM中读写数据,ARM或其它硬件从寄存器读取数据要快的多的多的多...因此编译器会“自作主张”的将某些变量读到寄存器中,再次运算时也优先从寄存器中读取,上面的例子就是这样。解决这样的方法是用关键字volatile修饰你不想让编译器优化的变量,明白的告诉编译器:你不准优化我,每次使用我你都要本本分分的从RAM中读取或写入RAM。
所以先不要笑,我是不会犯这种错误的,之所以从这里说起,是为了照顾下还不知道volatile关键字的。。。
其实在timer.c中我是这样定义统计定时器中断次数变量的:
unsigned int volatile unIdleCount;但是,在timer.h中,我确偷了个懒,声明这个变量的代码如下:
extern unsigned int unIdleCount;
没有使用关键字volatile,在keil MDK V5.54下编译,默认优化级别,然后查看代码的反汇编,如下所示:
122: unIdleCount=0;
123:
0x00002E10 E59F11D4 LDR R1,[PC,#0x01D4]
0x00002E14 E3A05000 MOV R5,#key1(0x00000000)
0x00002E18 E1A00005 MOV R0,R5
0x00002E1C E5815000 STR R5,[R1]
124: while(unIdleCount!=200); //延时2S钟
125:
0x00002E20 E35000C8 CMP R0,#0x000000C8
0x00002E24 1AFFFFFD BNE 0x00002E20
可以看出,这个反汇编代码居然和没加volatile关键字的时候一模一样!!代码还是会在while出陷入死循环。
现在,应该知道我要表达的意思了吧,如果引用的变量声明中没有使用volatile关键字修饰,即便定义这个变量的时候使用了volatile关键字修饰,MDK编译器照样优化掉它!
将timer.h中的声明更改为:
extern unsigned int volatile unIdleCount;
同样环境下编译,查看反汇编代码,如下所示:
122: unIdleCount=0;
123:
0x00002E10 E59F01D4 LDR R0,[PC,#0x01D4]
0x00002E14 E3A05000 MOV R5,#key1(0x00000000)
0x00002E18 E5805000 STR R5,[R0]
124: while(unIdleCount!=200); //延时2S钟
125:
0x00002E1C E5901000 LDR R1,[R0]
0x00002E20 E35100C8 CMP R1,#0x000000C8
0x00002E24 1AFFFFFC BNE 0x00002E1C
看最后三句汇编代码,发现多了一个载入汇编指令LDR,这个指令在每次循环中都将变量unIdleCount从RAM中读出到寄存器R1中,然后R1的值再和0xC8比较。这才是符合逻辑的需要的代码。
以这个为例子,一是说明关键字volatile,另外也提下这个有趣的编译器,不得不说,她好认真。再另外,我要买本编译原理的书看看了.
摘自个人博客---有趣的MDK细节:http://blog.csdn.net/zhzht19861011/article/details/7745151
现则反过来想想,原因还是很简单的,MDK编译多个文件时是分别编译,最后再用链接器链接,当编译的时候一个模块引用另外一个模块的变量,完全是靠的变量声明,如果声明都不加volatile,那么引用的模块肯定会把变量当成普通变量的,再反推一下,如果原变量没有加volatile,但是声明的时候加了volatile,是不是引用的模块会将这个变量当成volatile型变量呢.
|
|