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

一:Volatile的定义:

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

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

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

例一:

staticinti=0;

intmain(void)
{
...
while(1)
{
if(i)dosomething();
}
}

/*Interruptserviceroutine.*/
voidISR_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).下面的函数有什么错误:
intsquare(volatileint*ptr)
{
return*ptr**ptr;
}
下面是答案:
1).是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2).是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3).这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
intsquare(volatileint*ptr)
{
inta,b;
a=*ptr;
b=*ptr;
returna*b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
longsquare(volatileint*ptr)
{
inta;
a=*ptr;
returna*a;
}


例三:
intvolatilenVint;
>>>>当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatileinti=10;
inta=i;
...
//其他代码,并未明确告诉编译器,对i进行过操作
intb=i;
>>>>volatile指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
>>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
>>>>首先,用classwizard建一个win32console工程,插入一个voltest.cpp文件,输入下面的代码:
>>
#i nclude<stdio.h>
voidmain()
{
inti=10;
inta=i;
printf("i=%d",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm{
movdwordptr[ebp-4],20h
}
intb=i;
printf("i=%d",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i=10
i=32
然后,在release版本模式运行程序,输出结果如下:
i=10
i=10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把i的声明加上volatile关键字,看看有什么变化:
#i nclude<stdio.h>
voidmain()
{
volatileinti=10;
inta=i;
printf("i=%d",a);
__asm{
movdwordptr[ebp-4],20h
}
intb=i;
printf("i=%d",b);
}
分别在调试版本和release版本运行程序,输出都是:
i=10
i=32
这说明这个关键字发挥了它的作用!

四:const和volatile可以一起用吗

编译期就是<wbr><wbr> C<wbr><wbr> 编译器将<wbr><wbr> 源代码转化为<wbr><wbr> 汇编再到机器代码<wbr><wbr> 的过程。而运行期就是<wbr><wbr> 实际的机器代码在CPU执行<wbr><wbr> 的过程。很多书上说的东西,其实都只是指编译期进行的事情。<wbr><wbr><br> const<wbr><wbr> 和<wbr><wbr> volatile<wbr><wbr> 也一样,<wbr><wbr><br> 所谓的<wbr><wbr> const<wbr><wbr> ,只是编译器保证在<wbr><wbr> C的“源代码”里面,没有对该变量进行修改的地方,而实际运行的时候则不是<wbr><wbr> 编译器<wbr><wbr> 所能管的了。<wbr><wbr><br> 同样,volatile的所谓“可能被修改”,是指“在运行期间”可能被修改。也就是告诉编译器,这个变量不是“只”会被这些<wbr><wbr> C的“源代码”所操纵,其它地方也有操纵它们的地方。所以,C编译器就不能随便对它进行优化了。<br><br> const<wbr><wbr> volatile禁止编译器优化,所谓编译器优化是指当一个变量被声明为const时,编译器认为该变量在某一段代码(如一个函数)中不会发生改变,就会将该变量存储到CPU的寄存器,从CPU寄存器读写数据的速度要远远快于从内存读取数据。<wbr><wbr><br> const<wbr><wbr> volatile禁用了编译器优化,也就是说,不允许将该数据保存到CPU寄存器。<wbr><wbr><br> 保存到CPU寄存器的变量可能在某些情况下被改编,例如,另一个线程可能会改变该寄存器得值,<wbr><wbr> 这样就会导致你原本以为是const的变量发生了改变,导致了bug。使用const<wbr><wbr> volatile声明就避免了这种情况。</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

如果你把上面的都理解了的话,相信你在以后会有很大的收益!!! 希望大家也能提出自己的想法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值