volatile 用法以及大家遇到的有关问题

volatile 用法以及大家遇到的问题

一:Volatile的定义:

被volatile所修饰的变量,编译器在读取这个变量的值时就不会进行优化。因为编译器会认为这个变量是“易变”的,所以直接访问该变量的原始地址而不是寄存器。

 

有人会问什么是编译器优化,听我细细道来:

所谓的编译器优化是指在没有volatile修饰的变量,编译器会认为该变量不会被其他程序或者硬件修改,所以编译器会将变量缓存到寄存器避免访问内存,因为访问寄存器的速度远大于内存。昏了吧!!来个例子说明:请仔细看,结合刚才说的,不要直接看解释哦!

例一:

static int i=0; 

int main(void) 

... 
while (1) 

if (i) dosomething(); 



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

i=1; 


看出这个程序的问题了吗。首先说明这个程序是不会达到预期的目的。因为 i没有被Volatile修饰。

程序原本是想当i的值被中断服务程序修改为1后,就可以执行dosomething()函数,但由于编译器的优化,编译器读到的i值其实一直都是i缓存到寄存器里的值,即使i的真实的值被改变了,而编译器读到的值依然是“i的旧值”。所以if(i)一直为假。  

如果我们在定义i的时候,我们加了volatile修饰,程序就会达到预期目标。  能想到为什么了吗?

因为加了volatile修饰的i,编译器就不会对它优化,那么编译器每次读到的 i 值,都是通过直接访问i的“原始地址”所得到的。

 

举一个不太准确的例子:  

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

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

二:会使用volatile修饰的三种情况

1) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;(如:状态寄存器)

2) 在不同的进程之间共用全局变量;

3) 在中断服务程序中访问全局变量;

首先说下2) 和3),它的使用情况和例一是一样的,大家可以想想,我再这里不多说。

 

着重讲一下1),对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为C语言并没有提供真正的端口的概念。如果是内存映射,那就方便的多了。 以 #define IOPIN           (*((volatile unsigned long *) 0xE0028000))     为例:作为一个宏定义语句,define是定义一个变量或常量的伪指令。首先( volatile unsigned long * )的意思是将后面的那个地址强制转换成 volatile unsigned long * ,unsigned long * 是无符号长整形,volatile 是一个类型限定符,如const一样,当使用volatile限定时,表示这个变量是依赖系统实现的,以为着这个变量会被其他程序或者计算机硬件修改,由于地址依赖于硬件,volatile就表示他的值会依赖于硬件。volatile 类型是这样的,其数据确实可能在未知的情况下发生变化。比如,硬件设备的终端更改了它,现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中,这样,在某些条件下,程序就可以直接访问这些私有内存了。另外,比如共享的内存地址,多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。归纳起来如下:

1. volatile变量可变 允许除了程序之外的比如硬件来修改他的内容   
2. 访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消

 

三:实例(网上找的)

例二:假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。 
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。 
    2). 一个指针可以是volatile 吗?解释为什么。 
    3). 下面的函数有什么错误: 
         int square(volatile int *ptr) 
         { 
              return *ptr * *ptr; 
         } 
    下面是答案: 
    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*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; 
     } 


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

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

int b = i; 
printf("i= %d",b); 
}       
然后,在调试版本模式运行程序,输出结果如下: 
i = 10 
i = 32 
然后,在release版本模式运行程序,输出结果如下: 
i = 10 
i = 10 
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化: 
#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版本运行程序,输出都是: 
i = 10 
i = 32 
这说明这个关键字发挥了它的作用!

 

四:const和volatile可以一起用吗

编译期就是    编译器将   源代码转化为   汇编再到机器代码   的过程。而运行期就是   实际的机器代码在CPU执行   的过程。很多书上说的东西,其实都只是指编译期进行的事情。  
const   和   volatile   也一样,  
所谓的   const   ,只是编译器保证在   C的“源代码”里面,没有对该变量进行修改的地方,而实际运行的时候则不是   编译器   所能管的了。  
同样,volatile的所谓“可能被修改”,是指“在运行期间”可能被修改。也就是告诉编译器,这个变量不是“只”会被这些   C的“源代码”所操纵,其它地方也有操纵它们的地方。所以,C编译器就不能随便对它进行优化了。

const   volatile禁止编译器优化,所谓编译器优化是指当一个变量被声明为const时,编译器认为该变量在某一段代码(如一个函数)中不会发生改变,就会将该变量存储到CPU的寄存器,从CPU寄存器读写数据的速度要远远快于从内存读取数据。  
const   volatile禁用了编译器优化,也就是说,不允许将该数据保存到CPU寄存器。  
保存到CPU寄存器的变量可能在某些情况下被改编,例如,另一个线程可能会改变该寄存器得值,   这样就会导致你原本以为是const的变量发生了改变,导致了bug。使用const   volatile声明就避免了这种情况。

 

如果你把上面的都理解了的话,相信你在以后会有很大的收益!!!  希望大家也能提出自己的想法。