记录在项目中遇到被频繁更改的变量读取值错误的bug:
这些变量在main.c中声明,被一些函数调用和赋值,
同时在定时器中断函数文件中引用,中断处理函数利用它们进行计数并清零,实现控制时间线功能,
测试时发现与实际设定的时间不符,通过调试发现count变量有时没有正确调用更新的值,想到volatile和编译器相关知识点:这些变量被频繁地跨文件赋值和调用,变量赋值或取值的操作可能在编译过程中被编译器优化,用volatile关键字声明的变量,在每次对其值进行引用的时候都会从原始地址取值,用来消除编译器的优化。补充volatile声明后bug解决。
例:摘自博客C语言-volatile
在中断服务程序中,经常会修改一些全局变量值,来作为主程序中的判断条件。例如,在串口中断服务程序中,可能会检测是否接收到了ETX(假如是消息的结束标识符)字符。如果接收到了ETX,ISR设置一个全局标志位。
错误的做法:
在关闭编译器优化的情况下,程序可能执行正常。然而,任何像样点而优化都会“break”这段程序。问题是编译器并不知道etx_rcvd可能被ISR中被修改。编译器只知道,表达式!ext_rcvd始终为真,你讲用于无法退出循环。结果,循环后面的代码可能被编译器优化掉。
幸运的话,你的编译器可能会发出警告;不幸的话,(或者你不认真的查看编译器警告),你的程序无法正常执行。当然,你可以责怪编译器执行了“糟糕的优化”。
解决方式是,将变量etx_rcvd声明为volatile,所有问题(当然,也可能是部分问题)就消失了。
volitail关键字使用笔记
volatile原意是“易变的”,在嵌入式环境中用volatile关键字声明的变量,在每次对其值进行引用的时候都会从原始地址取值。由于该值“易变”的特性所以,针对其的任何赋值或者获取值操作都会被执行(而不会被优化)。由于这个特性,所以该关键字在嵌入式编译环境中经常用来消除编译器的优化,可以分为以下三种情景:
1)修饰硬件寄存器;
2)修饰中断服务函数中的非自动变量;
3)在有操作系统的工程中修饰会被多个应用修改的变量。
volatile关键字的写法:
声明一个变量为volatile,可以在数据类型之前或之后加上关键字volatile。下面的语句,把x声明为一个volatile的整型。
volatile int x;
int volatile x;
把指针指向的变量声明为volatile很常见,尤其是I/O寄存器的地址映射。下面的语句,把pReg声明为一个指向8-bit无符号指针,指针指向的内容为volatile。
volatile uint8_t * pReg;
uint8_t volatile * pReg;
volatile的指针指向非volatile的变量很少见,语法是:
int * volatile p;
注意点:
频繁地使用 volatile
很可能会增加代码尺寸和降低性能,因为它频繁地访问内存,而非缓存或寄存器,因此要合理的使用 volatile
。
作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值,volatile处理结果:直接存取原始内存地址的值。
“一些编译器允许把所有的变量隐式的声明为volatile,请抵制这种诱惑,因为它会令你不再思考,当然也会导致生成低效的代码。”