C语言关键字volatile是一个危险的东东,笔者再用ADS做S3C2440定时器中断实验就因为这个关键字出了错。出现错误情况的准确描述是:定义一个变量时没有用volatile关键字,而且紧接着while(1)循环里边就有对这个变量的读操作。
这个实验想实现的功能是:定时时间为1s,用一个led灯显示这个时间,亮一秒钟,然后再灭一秒钟。程序实现思路是:开irq中断,开定时器0中断,并设置定时器0中断时间为1s;在中断服务程序中利用一个全局变量flag来传递定时时间到信号,每中断一次flag翻转一次;主循环中读取flag标志,根据flag标志决定led灯的亮灭。
中断服务程序
void __irq Timer0_Isr(void) { flag=!flag; rSRCPND|=1<<10; rINTPND|=1<<10; }
flag定义在主程序中
unsigned int flag=0;
主循环程序
int Main() { IO_Init(); while(1){ if(flag){ Led1_Off(); } else{ Led1_On(); } } return(0);
}
经测试定时1s,中断服务程序都是能正常工作的,但是led不能闪烁,一直亮。试了很多办法,无果。
后来将变量的定义更改了一下,加一个关键字volatile。就解决了问题。但是,什么原因不得而知。
volatile unsigned int flag=0;
走投无路的情况下,只能求助于反汇编代码。
对比源程序
对比反汇编(图片太大,网页浏览时无法完全显示,可以对着图片单击右键,选择图片另存为桌面查看)
看到反汇编代码,很容易知道错误出现在哪儿。没加volatile时候,执行while循环需要重新读取flag的值时,不是从flag对应的内存单元中读的,而是读取保存flag临时数据的r2。虽然中断服务程序会将flag值更改,但是由于读取的是r2而且r2在主循环中始终不变,也就是说不能读到中断服务程序对flag的更新,所以led灯也不可能改变。
当加了volatile后,执行while循环需要重新读取flag的值时,是从flag对应的内存单元中读的,所以主循环能读到中断服务程序对flag的更新,led也能正常工作了。
结论:ADS对加没加volatile的变量处理是有区别的,但是我认为ADS对这种情况的处理不是很正确
之所以这样说,我源于下面三个实验。我用keil和gcc分别作了类似上边的实验,看编译器对没有加volatile的变量处理情况,发现这两个编译器都能正确编译,而唯独ADS对没有加volatile的变量处理的过分。
keil对没有加volatile的变量处理
测试程序
反汇编代码
gcc对没有加volatile的变量处理
第三个实验,在 if(flag) 前边加一个delay_time()函数,发现能够正常工作
试想,ADS仅仅因为在 if(flag)前边加了一个delay()函数,就更改了它的策略,我真觉得ADS对这种情况(定义一个变量时没有用volatile关键字,而且紧接着while(1)循环里边就有对这个变量的读操作)处理的有问题。而且,我认为编译器对volatile的处理好像也不是这样的。我对ADS的编译还存在疑惑,我对编译器对volatile的处理还存在疑惑。
附实验源码下载地址:timeirq.zip