volatile关键字的作用

volatile关键字的作用(来自百度百科)

include "stdafx.h"
#include <iostream>
using namespace std;


#if 0

----volatile关键字的作用:

C/C++中的 volatile 关键字和 const 对应,用来修饰变量 volatile 关键字是一种类
型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系
统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就
不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt;
当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,
即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
(防止优化编译器把变量从内存装入CPU寄存器中, volatile的意思是让编译器每次操作
	该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值)

例1:简单地说就是防止编译器对代码进行优化。比如如下程序:
XBYTE[2] = 0x55;
XBYTE[2] = 0x56;
XBYTE[2] = 0x57;
XBYTE[2] = 0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,
但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2] = 0x58(即忽略前三条语
句,只产生一条机器代码)。如果键入volatile,则编译器会逐一地进行编译并产生相
应的机器代码(产生四条代码)

例2:精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,
而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1.并行设备的硬件寄存器(如:状态寄存器)
2.一个中断服务子程序中会访问到的非自动变量(Non - automatic variables)
3.多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、
中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。


----volatile使用的几个场景:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile(一个中断服务子程序中会访问到的非自动变量(Non - automatic variables))
2、多任务环境下各任务间共享的标志应该加volatile;(多线程应用中被几个任务共享的变量)
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义
4.并行设备的硬件寄存器

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


----volatile的相关理解:

volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释有点误导人;
“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量
就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;而用volatile定义
之后,其实这个变量就不会因外因而变化了,可以放心使用了,volatile关键字是一种类型
修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件
或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,
从而可以提供对特殊地址的稳定访问。

当要求使用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文件,输入下面的代码:

#include<stdio.h>

void main(int argc, char *argv[])
{
	int i = 10;  //要想保持实时性读取,需要改为volatile 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值


----其他例子:

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

----volatile的相关模式(详情见百度百科)

1.状态标志
2.一次性安全发布
3.独立观察
4."volatile bean"模式
5.开销较低的读--写锁策略


----volatile与锁机制的比较

使用 volatile变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用
相应的锁简单得多。使用 volatile变量次要原因是其性能:某些情况下,volatile 变量同
步机制的性能要优于锁。与锁相比,Volatile变量是一种非常简单但同时又非常脆弱的同步
机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件,
即变量真正独立于其他变量和自己以前的值,在某些情况下可以使用 volatile 代替 synchronized 
来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错

Volatile变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现
volatile变量的最新值。Volatile变量可用于提供线程安全,但是只能应用于非常有限的一组
用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 
还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <= end”)。
出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile变量而不是锁。当使用 volatile变量而非锁时,
某些习惯用法(idiom)更加易于编码和阅读。此外,volatile变量不会像锁那样造成线程阻塞,
因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile变量还
可以提供优于锁的性能优势。

#endif

猜你喜欢

转载自blog.csdn.net/SwordArcher/article/details/81511370