打印
[应用相关]

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

[复制链接]
1469|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
vigous1|  楼主 | 2015-3-28 21:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一: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
这说明这个关键字发挥了它的作用!


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

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

使用特权

评论回复
板凳
energy1| | 2015-3-28 21:38 | 只看该作者
如果把上面的都理解了的话,相信在以后会有很大的收益

使用特权

评论回复
地板
powerful1| | 2015-3-28 21:56 | 只看该作者
线程可能会改变该寄存器得值, 这样就会导致你原本以为是const的变量发生了改变,导致了bug。使用const   volatile声明就避免了这种情况

使用特权

评论回复
5
quray1985| | 2015-3-29 09:38 | 只看该作者
讲 的很详细
我只知道在用到全局变量的时候才用这个

使用特权

评论回复
6
搞IT的| | 2015-3-29 10:25 | 只看该作者
多谢楼主总结啊!!

使用特权

评论回复
7
lsarmlsarm| | 2015-3-29 21:00 | 只看该作者
学习

使用特权

评论回复
8
dianzilc| | 2015-6-13 08:31 | 只看该作者
非常感谢楼主,刚好前两天写程序时就遇到了这样的问题,当时一直纳闷为什么条件没有被触发,现在知道要怎么改了!

使用特权

评论回复
9
dianzilc| | 2015-6-13 09:02 | 只看该作者
本帖最后由 dianzilc 于 2015-6-13 09:07 编辑

请问下楼主,一般写单片机程序会用定时器定一个时基,在定时时间到后都会在中断程序里将标志位置1,比如Time1msFlag = 1;。然后在其它程序中查看该标志位是否为1,并执行相关程序。如果按文中说的,应该在定义这个标志位时加volatile,不过看过的程序基本都没这样写,而且程序也能正常运行。那这个要怎么理解呢?我的理解是这个变量一般只在一个函数中用到所以可以不加volatile,如果是在多个函数中都会改变这个变量的话就应该加volatile,不懂这样理解是否正确?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

88

主题

427

帖子

15

粉丝