102.volatile关键字的作用?

发布时间 2023-07-11 15:53:03作者: CodeMagicianT

102.volatile关键字的作用?

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

volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

☀警告

volatile的确切含义与机器有关,只能通过阅读编译器文档来理解。要想让使用了volatile的程序在移植到新机器或新编译器后仍然有效,通常需要对该程序进行某些改变。

直接处理硬件的程序常常包含这样的数据元素,它们的值由程序直接控制之外的过程控制。例如,程序可能包含一个由系统时钟定时更新的变量。当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile。关键字volatile告诉编译器不应对这样的对象进行优化。(比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。)

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。多线程中被几个任务共享的变量需要定义为volatile类型。

1.volatile指针

volatile限定符的用法和const很相似,它起到对类型额外修饰的作用:

volatile int display_register;  // 该int值可能发生改变
volatile Task *curr_task;  // curr task指向一个volatile对象 
volatile int iax [max_size];  // iax的每个元素都是volatile 
volatile Screen bitmapBuf;  // bitmapBuf的每个成员都是volatile

const和volatile限定符互相没什么影响,某种类型可能既是const的也是volatile的,此时它同时具有二者的属性。

就像一个类可以定义const成员函数一样,它也可以将成员函数定义成volatile的。 只有volatile的成员函数才能被volatile的对象调用。

const限定符和指针的相互作用,在volatile限定符和指针之间也存在类似的关系。我们可以声明volatile指针、指向volatile对象的指针以及指向volatile对象的volatile指针:

volatile int v; // v是一个volatile int 
int *volatile vip; // vip是一个volatile指针,它指向int
volatile int *ivp; // ivp是一个指针,它指向一个volatile int
volatile int *volatile vivp; // vivp是一个volatile指针,它指向一个volatile int 
int intv;
int *ip = &v;//错误:必须使用指向volatile的指针
ivp = &v; //正确:ivp是一个指向volatile的指针
vivp = &v;//正确:vivp是一个指向volatile的volatile指针
v = intv;//正确:可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
intv = v;//错误

●和const一样,我们只能将一个 volatile对象的地址(或者拷贝一个指向volatile类型的指针)赋给一个指向volatile的指针。同时,只有当某个引用是volatile的时,我们才能使用一个volatile对象初始化该引用。

●C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

2.合成的拷贝对volatile对象无效

const和volatile的一个重要区别是我们不能使用合成的拷贝/移动构造函数及赋值运算符初始化volatile对象或从volatile对象赋值。合成的成员接受的形参类型是(非volatile)常量引用,显然我们不能把一个非volatile引用绑定到一个volatile对象上。

如果一个类希望拷贝、移动或赋值它的volatile对象,则该类必须自定义拷贝或移动操作。例如,我们可以将形参类型指定为const volatile引用,这样我们就能利用任意类型的Foo进行拷贝或赋值操作了:

class Foo
{
public:
	Foo(const volatile Foo&);//从一个volatile对象进行拷贝
	Foo& operator=(volatile const Foo&);//将一个volatile对象赋值给一个非volatile对象
	Foo& operator=(volatile const Foo&) volatile;//将一个volatile对象赋值给一个volatile对象
	//Foo类的剩余部分
};

尽管我们可以为volatile对象定义拷贝和赋值操作,但是一个更深层次的问题是拷贝volatile对象是否有意义呢?不同程序使用volatile的目的各不相同,对上述问题的同答与具体的使用目的密切相关。

3.多线程下的volatile

有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。

如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

4.保证变量在内存中的可见性

有数据一致性的隐患

5.禁止编译器做过度优化


6.禁止指令重排









voltile具有内存屏障的功能



参考资料来源:

阿秀、C++Primer、在划水里划水