现在的位置: 首页 > 综合 > 正文

C语言volatile关键字

2013年09月11日 ⁄ 综合 ⁄ 共 4791字 ⁄ 字号 评论关闭
volatile 是易变的、不稳定的意思。很多人根本就没见过这个关键字,不知道它的存在。也有很多程序员知道它的存在,但从来没用过它。我对它有种“杨家有女初长成,养在深闺人未识” 的感觉。

volatile 关键字和const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

先看看下面的例子:

int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为i 的值没有发生改变,所以在(1)语句时从内存中取出i 的值赋给j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k 赋值。编译器不会生成出汇编代码重新从内存里取i 的值,这样提高了效率。但要注意:(1)、(2)语句之间i 没有被用作左值才行。

再看另一个例子:
volatile int i=10;
int j = i;//(3)语句
int k = i;//(4)语句

volatile 关键字告诉编译器i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。这样看来,如果i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile 可以保证对特殊地址的稳定访问。但是注意:在VC++6.0 中,一般Debug 模式没有进行代码优化,所以这个关键字的作用有可能看不出来。你可以同时生成Debug
版和Release 版的程序做个测试。

明白volatile的初衷,最终决定什么时候真正需要使用volatile

1.volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
2.而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

这里还是花点时间说说编译器优化吧:
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

有兴趣可以看看这个分享,写的很是不错:http://blog.csdn.net/tigerjb/article/details/7427366

也许下面这个例子对你更有说服力

  1. #include <stdio.h>  
  2. void main()  
  3. {  
  4.     int i=10;  
  5.     int a = i;  
  6.       
  7.     printf("i= %d\n",a);  
  8.     //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道  
  9.     __asm {  
  10.         mov dword ptr [ebp-4], 20h  
  11.     }  
  12.       
  13.     int b = i;  
  14.     printf("i= %d\n",b);  
  15. }   

运行于debug模式下,结果如下

  1. i = 10  
  2. i = 32  

运行于release模式下,结果则不然

  1. i = 10  
  2. i = 10  

输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:

  1. #include <stdio.h>  
  2. void main()  
  3. {  
  4.     volatile int i=10;  
  5.     int a = i;  
  6.       
  7.     printf("i= %d\n",a);  
  8.     __asm {  
  9.         mov dword ptr [ebp-4], 20h  
  10.     }  
  11.       
  12.     int b = i;  
  13.     printf("i= %d\n",b);  
  14. }  

分别在调试版本和release版本运行程序,输出都是:

  1. i = 10  
  2. i = 32  

这样一来你总该已经知道什么时候该来麻烦volatile帮你禁止编译器一厢情愿的帮你进行优化了吧O(∩_∩)O~
待续。。。

volatile 关键字和const 一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

先看看下面的例子:

int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为i 的值没有发生改变,所以在(1)语句时从内存中取出i 的值赋给j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给k 赋值。编译器不会生成出汇编代码重新从内存里取i 的值,这样提高了效率。但要注意:(1)、(2)语句之间i 没有被用作左值才行。

再看另一个例子:
volatile int i=10;
int j = i;//(3)语句
int k = i;//(4)语句

volatile 关键字告诉编译器i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。这样看来,如果i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile 可以保证对特殊地址的稳定访问。但是注意:在VC++6.0 中,一般Debug 模式没有进行代码优化,所以这个关键字的作用有可能看不出来。你可以同时生成Debug
版和Release 版的程序做个测试。

明白volatile的初衷,最终决定什么时候真正需要使用volatile

1.volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
2.而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

这里还是花点时间说说编译器优化吧:
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

有兴趣可以看看这个分享,写的很是不错:http://blog.csdn.net/tigerjb/article/details/7427366

也许下面这个例子对你更有说服力

  1. #include <stdio.h>  
  2. void main()  
  3. {  
  4.     int i=10;  
  5.     int a = i;  
  6.       
  7.     printf("i= %d\n",a);  
  8.     //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道  
  9.     __asm {  
  10.         mov dword ptr [ebp-4], 20h  
  11.     }  
  12.       
  13.     int b = i;  
  14.     printf("i= %d\n",b);  
  15. }   

运行于debug模式下,结果如下

  1. i = 10  
  2. i = 32  

运行于release模式下,结果则不然

  1. i = 10  
  2. i = 10  

输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:

  1. #include <stdio.h>  
  2. void main()  
  3. {  
  4.     volatile int i=10;  
  5.     int a = i;  
  6.       
  7.     printf("i= %d\n",a);  
  8.     __asm {  
  9.         mov dword ptr [ebp-4], 20h  
  10.     }  
  11.       
  12.     int b = i;  
  13.     printf("i= %d\n",b);  
  14. }  

分别在调试版本和release版本运行程序,输出都是:

  1. i = 10  
  2. i = 32  

这样一来你总该已经知道什么时候该来麻烦volatile帮你禁止编译器一厢情愿的帮你进行优化了吧O(∩_∩)O~
待续。。。

抱歉!评论已关闭.