嵌入式C语言中的关键字volatile
嵌入式C语言中的关键字volatile
嵌入式C语言中的关键字volatile
- 嵌入式C语言中的关键字volatile
- 一. volatile关键字的概念
- 二. 不使用volatile关键字
- 三. 编译器优化介绍
- 四. volatile详解
- 五. 编译器优化举例
- 1)例:没有volatile关键字的优化
- 2)例:volatile关键字对形参的优化
- 六. volatile关键字使用
- 七. 总结
一. volatile关键字的概念
是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile关键字是一种类型修饰符,用它声明的类型变量表示不可以被某些编译器未知的因素更改(优化)。volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据,而不是从寄存器或者缓存中去读取数据。
二. 不使用volatile关键字
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
三. 编译器优化介绍
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。
再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。 对常规内存进行优化的时候,这些优化是透明的,而且效率很好。
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。
四. volatile详解
volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用volatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
五. 编译器优化举例
我们先来看下面的两个例子。
1)例:没有volatile关键字的优化
int i = 10;
int main(void){
int a, b;
a = i;
...//伪代码,里面不含有对 a 、 b 以及i的操作
b = i;
if(a == b){
printf("a = b");
}
else {
printf("a != b");
}
return 0;
}
int i = 10;
int main(void){
int a, b;
a = i;
...//伪代码,里面不含有对 a 、 b 以及 i的操作
b = i;
printf("a = b");
return 0;
}
在仅仅从main主函数来看,a == b是必然的,那么在什么情况,a 和 b不是必然相等呢?答案如下。
- i 是其他子线程与主线程共享的全局变量,其他子线程有可能修改 i 值;
- i 是中断函数与主函数共享的全局变量,中断函数有可能修改 i 值;
- i 属于硬件寄存器,CPU可能通过硬件直接改变 i 的值(例如寄存器的标志位)
也就说,如果满足了上面三个条件中的任何一个,那么就有可能出现a!=b的情况。这个时候为了防止编译器不必要的优化手段,就可以引入关键字volatile。
2)例:volatile关键字对形参的优化
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
这段代码的目的是用来返指针* ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
六. volatile关键字使用
volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。
volatile 常见的几个问题:
1、一个参数既可以是const还可以是volatile吗?
可以,例如只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它。
2、一个指针可以是 volatile 吗?
可以,当一个服务子程序修改一个指向一个 buffer 的指针时
3、C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用
volatile应该是在编译阶段,extern在链接阶段。
volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。
七. 总结
总结一下,如果不使用volatile,编译器可以更好的为我们优化代码,优化为性能或者效率更好的执行代码。但反过来,如果频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。
具体在嵌入式开发过程当中,有以下两种用法:
1)告诉compiler不能做任何优化
比如要往某一地址送两指令:
代码如下:
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...;
*ip = 2;
结果第一个指令丢失。
如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
volatile int *ip = …;
*ip = 1; *ip = 2;
即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
2)表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
如复制代码 代码如下:
volatile char a;
a=0;
while(!a) {
//do some things;
}
doother();
//如果没有 volatile则doother()不会被执行(编译器优化a的读取 认为a的值不改变恒为0)