volatile使用

11 篇文章 0 订阅
8 篇文章 0 订阅

这是个十分奇葩的问题,碰巧被我遇到了,我承认是我代码写的不够规范,但正是这个不规范的代码,才得以发现这个奇葩的事件。实在忍不住用了两个奇葩来形容。把过程简化一下,如下所述:

假如你的工程至少有两个.c文件,其中一个为timer.c,里面有个定时器中断程序,每10ms中断一次,定义一个变量来统计定时器中断次数:

  1. unsigned int unIdleCount;  
unsigned int unIdleCount;
还有一个timer.h文件,里面是一些timer.c模块的封装,其中变量unIdleCount就被封装在里面:

  1. extern unsigned int unIdleCount;  
extern unsigned int unIdleCount;

在main.c函数中,包含timer.h文件,并利用定时器变量 unIdleCount来精确延时2秒,代码如下:

  1. unIdleCount=0;  
  2.       
  3. while(unIdleCount!=200);   //延时2S钟  
unIdleCount=0;
    
while(unIdleCount!=200);   //延时2S钟
keil MDK V5.54下编译,默认优化级别,编译后下载到硬件平台。你会发现,代码在
  1. while(unIdleCount!=200);  
while(unIdleCount!=200);
处陷入了死循环。反汇编,代码如下:

  1.    122:     unIdleCount=0;   
  2.    123:        
  3. 0x00002E10  E59F11D4  LDR       R1,[PC,#0x01D4]  
  4. 0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)  
  5. 0x00002E18  E1A00005  MOV       R0,R5  
  6. 0x00002E1C  E5815000  STR       R5,[R1]  
  7.    124:     while(unIdleCount!=200);   //延时2S钟   
  8.    125:        
  9. 0x00002E20  E35000C8  CMP       R0,#0x000000C8  
  10. 0x00002E24  1AFFFFFD  BNE       0x00002E20  
   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中我是这样定义统计定时器中断次数变量的:

  1. unsigned int volatile unIdleCount;  
unsigned int volatile unIdleCount;
但是,在timer.h中,我确偷了个懒,声明这个变量的代码如下:

  1. extern unsigned int unIdleCount;  
extern unsigned int unIdleCount;
没有使用关键字volatile,在 keil MDK V5.54下编译,默认优化级别,然后查看代码的反汇编,如下所示:

  1.    122:     unIdleCount=0;   
  2.    123:        
  3. 0x00002E10  E59F11D4  LDR       R1,[PC,#0x01D4]  
  4. 0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)  
  5. 0x00002E18  E1A00005  MOV       R0,R5  
  6. 0x00002E1C  E5815000  STR       R5,[R1]  
  7.    124:     while(unIdleCount!=200);   //延时2S钟   
  8.    125:        
  9. 0x00002E20  E35000C8  CMP       R0,#0x000000C8  
  10. 0x00002E24  1AFFFFFD  BNE       0x00002E20  
   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中的声明更改为:

  1. extern unsigned int volatile unIdleCount;  
extern unsigned int volatile unIdleCount;
同样环境下编译,查看反汇编代码,如下所示:

  1.    122:     unIdleCount=0;   
  2.    123:        
  3. 0x00002E10  E59F01D4  LDR       R0,[PC,#0x01D4]  
  4. 0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)  
  5. 0x00002E18  E5805000  STR       R5,[R0]  
  6.    124:     while(unIdleCount!=200);   //延时2S钟   
  7.    125:        
  8. 0x00002E1C  E5901000  LDR       R1,[R0]  
  9. 0x00002E20  E35100C8  CMP       R1,#0x000000C8  
  10. 0x00002E24  1AFFFFFC  BNE       0x00002E1C  
   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比较。这才是符合逻辑的需要的代码。

其实如果好好看看编译原理的书,是不会犯这么低级的错误的,编译器是分文件编译,然后链接,文件A使用了文件B中定义的变量,在编译的时候,文件A是完全不知道文件B里面有什么东西的,只能通过文件B的接口文件(.h文件)来获得使用变量的属性.

以这个为例子,着重说明下关键字volatile,同时也要掌握编译原理的知识,用好手中的工具.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值