C语言中volatile的作用

news/2024/7/9 15:43:50 标签: 语言, c, 编译器, 优化, 多线程, 汇编
cle class="baidu_pl">
cle_content" class="article_content clearfix">
content_views" class="htmledit_views">
ca,sans-serif; font-size:13px">         转自 http://blog.21ic.com/user1/2949/archives/2007/35599.html
ca,sans-serif; font-size:13px">      一个定义为volatile的变量是说这变量可能会被意想不到地改变࿰c;这样࿰c;编译器就不会去假设这个变量的值了。精确地说就是࿰c;优化器在用到这个变量时必须每次都小心地重新读取这个变量的值࿰c;而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 
    1). 并行设备的硬件寄存器(如:状态寄存器) 
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
    3). 多线程应用中被几个任务共享的变量 
    回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道࿰c;所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。 
    假设被面试者正确地回答了这是问题(嗯࿰c;怀疑这否会是这样)࿰c;我将稍微深究一下࿰c;看一下这家伙是不是直正懂得volatile完全的重要性。 
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。 
    2). 一个指针可以是volatile 吗?解释为什么。 
    3). 下面的函数有什么错误: 
         int square(volatile int *ptr) 
         { 
              return *ptr * *ptr; 
         } 
    下面是答案: 
    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方࿰c;但是࿰c;由于*ptr指向一个volatile型参数࿰c;编译器将产生类似下面的代码: 
    int square(volatile int *ptr)  
    { 
         int a,b; 
         a = *ptr; 
         b = *ptr; 
         return a * b; 
     } 
    由于*ptr的值可能被意想不到地该变࿰c;因此a和b可能是不同的。结果࿰c;这段代码可能返不是你所期望的平方值!正确的代码如下: 
     long square(volatile int *ptr)  
     { 
            int a; 
            a = *ptr; 
            return a * a; 
     } 
ca,sans-serif; font-size:13px"> 讲讲我的理解: (欢迎打板子...~~!) 

关键在于两个地方:      
  
1. 编译器优化  (请高手帮我看看下面的理解) 

在本次线程内, 当读取一个变量时࿰c;为提高存取速度࿰c;编译器优化时有时会先把变量读取到一个寄存器中;以后࿰c;再取变量值时࿰c;就直接从寄存器中取值; 

当变量值在本线程里改变时࿰c;会同时把变量的新值copy到该寄存器中࿰c;以便保持一致 

当变量在因别的线程等而改变了值࿰c;该寄存器的值不会相应改变࿰c;从而造成应用程序读取的值和实际的变量值不一致 

当该寄存器在因别的线程等而改变了值࿰c;原变量的值不会改变࿰c;从而造成应用程序读取的值和实际的变量值不一致  


举一个不太准确的例子:  

发薪资时࿰c;会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事࿰c;没有即时登记࿰c;用了以前登记的银行卡号;刚好一个员工的银行卡丢了࿰c;已挂失该银行卡号;从而造成该员工领不到工资  

员工 -- 原始变量地址  
银行卡号 -- 原始变量在寄存器的备份  


2. 在什么情况下会出现(如1楼所说) 

    1). 并行设备的硬件寄存器(如:状态寄存器)  
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)  
    3). 多线程应用中被几个任务共享的变量  
     

补充: volatile应该解释为“直接存取原始内存地址”比较合适࿰c;“易变的”这种解释简直有点误导人;  

“易变”是因为外在因素引起的࿰c;象多线程c;中断等࿰c;并不是因为用volatile修饰了的变量就是“易变”了࿰c;假如没有外因࿰c;即使用volatile定义࿰c;它也不会变化; 

而用volatile定义之后࿰c;其实这个变量就不会因外因而变化了࿰c;可以放心使用了; 大家看看前面那种解释(易变的)是不是在误导人 


------------简明示例如下:------------------ 

volatile关键字是一种类型修饰符࿰c;用它声明的类型变量表示可以被某些编译器未知的因素更改࿰c;比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量࿰c;编译器对访问该变量的代码就不再进行优化c;从而可以提供对特殊地址的稳定访问。 
使用该关键字的例子如下: 
int volatile nVint; 
>>>>当要求使用volatile 声明的变量的值的时候࿰c;系统总是重新从它所在的内存读取数据࿰c;即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 
例如: 
volatile int i=10; 
int a = i; 
... 
//其他代码࿰c;并未明确告诉编译器c;对i进行过操作 
int b = i; 
>>>>volatile 指出 i是随时可能发生变化的࿰c;每次使用它的时候必须从i的地址中读取࿰c;因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是࿰c;由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作࿰c;它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来࿰c;如果i是一个寄存器变量或者表示一个端口数据就容易出错࿰c;所以说volatile可以保证对特殊地址的稳定访问。 
>>>>注意࿰c;在vc6中࿰c;一般调试模式没有进行代码优化c;所以这个关键字的作用看不出来。下面通过插入汇编代码࿰c;测试有无volatile关键字࿰c;对程序最终代码的影响: 
>>>>首先࿰c;用classwizard建一个win32 console工程࿰c;插入一个voltest.cpp文件࿰c;输入下面的代码: 
>> 
#i nclude <stdio.h> 
void main() 

int i=10; 
int a = i; 
printf("i= %d",a); 
//下面汇编语句的作用就是改变内存中i的值࿰c;但是又不让编译器知道 
__asm { 
mov dword ptr [ebp-4], 20h 

int b = i; 
printf("i= %d",b); 
}       
然后࿰c;在调试版本模式运行程序࿰c;输出结果如下: 
i = 10 
i = 32 
然后࿰c;在release版本模式运行程序࿰c;输出结果如下: 
i = 10 
i = 10 
输出的结果明显表明࿰c;release模式下࿰c;编译器对代码进行了优化c;第二次没有输出正确的i值。下面࿰c;我们把 i的声明加上volatile关键字࿰c;看看有什么变化: 
#i nclude <stdio.h> 
void main() 

volatile int i=10; 
int a = i; 
printf("i= %d",a); 
__asm { 
mov dword ptr [ebp-4], 20h 

int b = i; 
printf("i= %d",b); 
}       
分别在调试版本和release版本运行程序࿰c;输出都是: 
i = 10 
i = 32 
这说明这个关键字发挥了它的作用! 

------------------------------------ 


volatile对应的变量可能在你的程序本身不知道的情况下发生改变 
比如多线程的程序࿰c;共同访问的内存当中࿰c;多个程序都可以操纵这个变量 
你自己的程序࿰c;是无法判定合适这个变量会发生变化 
还比如࿰c;他和一个外部设备的某个状态对应࿰c;当外部设备发生操作的时候࿰c;通过驱动程序和中断事件࿰c;系统改变了这个变量的数值࿰c;而你的程序并不知道。 
对于volatile类型的变量࿰c;系统每次用到他的时候都是直接从对应的内存当中提取࿰c;而不会利用cache当中的原有数值࿰c;以适应它的未知何时会发生的变化࿰c;系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。 

-------------------------------------------------------------------------------- 

典型的例子 
for ( int i=0; i<100000; i++); 
这个语句用来测试空循环的速度的 
但是编译器肯定要把它优化掉࿰c;根本就不执行 
如果你写成  
for ( volatile int i=0; i<100000; i++); 
它就会执行了 

volatile的本意是“易变的”  
由于访问寄存器的速度要快过RAM࿰c;所以编译器一般都会作减少存取外部RAM的优化。比如: 

static int i=0; 

int main(void) 

... 
while (1) 

if (i) dosomething(); 



/* Interrupt service routine. */ 
void ISR_2(void) 

i=1; 


程序的本意是希望ISR_2中断产生时࿰c;在main当中调用dosomething函数࿰c;但是࿰c;由于编译器判断在main函数里面没有修改过i࿰c;因此 
可能只执行一次对从i到某寄存器的读操作࿰c;然后每次if判断都只使用这个寄存器里面的“i副本”࿰c;导致dosomething永远也不会被 
调用。如果将将变量加上volatile修饰࿰c;则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。 

一般说来࿰c;volatile用在如下的几个地方: 

1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 

2、多任务环境下各任务间共享的标志应该加volatile; 

3、存储器映射的硬件寄存器通常也要加volatile说明࿰c;因为每次对它的读写都可能由不同意义; 

另外࿰c;以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写)࿰c;在1中可以通过关中断来实 
现࿰c;2中可以禁止任务调度࿰c;3中则只能依靠硬件的良好设计了。 
  
cle>

http://www.niftyadmin.cn/n/1713584.html

相关文章

C语言漂亮的宏定义

转自http://blog.21ic.com/user1/2949/archives/2007/35550.html 写好C语言&#xff0c;漂亮的宏定义很重要&#xff0c;使用宏定义可以防止出错&#xff0c;提高可移植性&#xff0c;可读性&#xff0c;方便性 等等。下面列举一些成熟软件中常用得宏定义。。。。。。 1&…

HTML期末大作业: 关于饮食餐饮HTML网页设计 ~咖啡网站pc端带轮播(5个页面) 学生DW网页设计作业源码

HTML期末大作业: 关于饮食餐饮HTML网页设计 ~咖啡网站pc端带轮播(5个页面) 学生DW网页设计作业源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大&#xff1f;HTML网页作业无从下手&#xff1f;网页要求的总数量太多&#xff1f;没有合适的模板&#xff1f…

Keil编译时出现 Error:Failed to execute 'BIN40/Armcc'

在系统 开始——运行里输入 cmd&#xff0c;然后输入 cd C:\KEIL\ARM\BIN40\ 转到armcc.exe所在的文件夹&#xff0c;然后输入命令&#xff1a; armcc 查看输出&#xff0c;如果编程环境正常&#xff0c;应该输出正常信息。但我的输出是&#xff1a; 可见是License出了问题…

函数中有多个return?C语言中,一个函数可以有几个返回值?

在移植wifi的代码时&#xff0c;遇到了一些与下面类似的函数&#xff0c;这些函数中有不只一个return&#xff0c;这可是个新鲜的玩意儿&#xff1a; int func (int b) {int a5;if (a>b)return a;elsereturn b;return 0; }原来C/C 中&#xff0c;reutrn语句是这样定义的&am…

linux select poll epoll

http://bookjovi.iteye.com/blog/1186736 Linux中异步IO等待无非就三个系统调用&#xff1a;select&#xff0c; poll和epoll。很多人无法理解三种调用的区别&#xff0c;或不够了解&#xff0c;今天就结合Linux kernel code详细描述三个的区别&#xff01; select: select 的…

HTML期末大作业—— 游戏网页(5个页面) ~ 全屏游戏美术大赛作品征集网页 HTML+CSS+JS ~ web课程设计网页规划与设计

HTML期末大作业—— 游戏网页(5个页面) ~ 全屏游戏美术大赛作品征集网页 HTMLCSSJS ~ web课程设计网页规划与设计 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大&#xff1f;HTML网页作业无从下手&#xff1f;网页要求的总数量太多&#xff1f;没有合适的模…

MP4文件quick start探究

最近在Flash上实现MP4文件点播时&#xff0c;发现部分MP4文件在原有的播放器上无法播放&#xff0c;经过定位发现&#xff0c;原来是MP4 BOX搞的鬼&#xff01; 什么是MP4 BOX&#xff1f; 对于一般的MP4文件&#xff0c;BOX顺序为&#xff1a;ftyp-moov-free-mdat. 而对于部…

64位Ubuntu无法安装 lib32stdc++6问题

在Ubuntu下安装Android开发环境&#xff0c;一切就绪后&#xff0c;项目编译出现问题&#xff1a; 错误信息为&#xff1a;Cannot run program “/home/xxxx/android/android-sdk/platform-tools/adb”: error2, 没有那个文件或目录 原因&#xff1a;由于是64位的linux系统&a…