引子
突然想到一个解释volatile关键字的很好的例子。就当做引子,来讲一讲这个关键字吧。
const int iNum = 0;
int *iPtr = const_cast<int*>(&iNum);
*iPtr = 5;
cout << "iNum = "<< iNum << endl;
cout << "*iPtr = " << *iPtr << endl;
cout << "addr iNum: " << &iNum << endl;
cout << "addr iPtr: " << iPtr << endl;
运行结果:
iNum = 0
*iPtr = 5
addr iNum: 0x22fef8
addr iPtr: 0x22fef8
输出结果是不是很出人意料!对于相同地址上的值,为什么读出来的数值会不一样呢?
我们再添加上关键字volatile,再来看一下结果:
const int volatile iNum = 0;
int *iPtr = const_cast<int*>(&iNum);
*iPtr = 5;
cout << "iNum = "<< iNum << endl;
cout << "*iPtr = " << *iPtr << endl;
cout << "addr iNum: " << &iNum << endl;
cout << "addr iPtr: " << iPtr << endl;
此时编译有一条警告信息:
|warning: the address of 'iNum' will always evaluate as 'true' [-Waddress]|
运行结果:
iNum = 5
*iPtr = 5
addr iNum: 1
addr iPtr: 0x22ff08
我们看到二者的值终于同步了,但是变量iNum的地址值却变成了1,这是为什么呢?
The address of 'iNum' will always evaluate as 'true' [-Waddress]
先来解决“变量iNum的地址值却变成了1”这个问题。
这就要涉及到operator<<运算符重载的问题了。operator<<运算符的重载形式中,有对应const void*的,但是没有对应volatile void*的。同时我们知道,在C++的隐式转换中,不会移除const或者volatile限定。
我们通过查看文档§27.7.3.6.2 Arithmetic inserters [ostream.inserters.arithmetic]
operator<<(int _Val)
operator<<(basic_ostream<char, _Traits>& _Ostr, const char *_Val)
operator<<(const void *_Val)
char*类型的指针,不会被隐式转换为void*.
对于大多数的指针类型,iostream会将其隐式地转换为void * 以用于显示,但是不会对volatile型指针进行转换。正因为如此,C++默认将volatile指针转换为bool类型。
可以通过以下程序进行验证:
#include <iostream>
void foo(const void *) {
std::cout << "pointer \n";
}
void foo(bool ) {
std::cout << "bool \n";
}
int main() {
volatile int iNum;
foo(&iNum);
std::cout << "The addr of volatile iNum is " << &iNum << "\n";
int iNum2;
foo(&iNum2);
std::cout << &iNum2 << "\n";
void foo(volatile void*); //声明形参为volatile void*的foo函数
foo(&iNum);
}
void foo(volatile void *) {
std::cout << "now volatile pointer \n";
}
输出结果为:
bool
The addr of volatile iNum is 1
pointer
0x22fef8
now volatile pointer
上面这个例子再次印证了这句话,即:对于大多数的指针类型,iostream会将其隐式地转换为void * 以用于显示,但是不会对volatile型指针进行转换。正因为如此,C++默认将volatile指针转换为bool类型。
正因为C++编译器不会将volatile型指针变量隐式转换为void*类型,即不会将volatile属性去掉,而是按照默认情况隐式转换为bool类型,所以对于operator<<的重载函数来说,operator<<(bool val); 函数匹配成功。显然,这不是我们想要的,所以我们会得到一个编译器警告:
|warning: the address of 'iNum' will always evaluate as 'true' [-Waddress]|
所以如果想输出volatile int iNum 的地址,就要手动显式强制转换为void *,当然,强制转换为int *也可以。
std::cout << (void*)&iNum;
这显然不是最佳的办法。不知道为什么标准中不增加一项。如果增加了这一项,将会美好很多。确实不解。
operator<<(const volatile void* val);
OK.关于警告的问题讲完了。
volatile关键字
下面进入正题,开始讲解volatile关键字。
先引用C++之父Bjarne Stroustrup对volatile关键字的一段描述。
volatile说明符告诉编译器,该对象可能会被第三方修改,尽管在编程语言中并没有去主动修改过该对象。所以编译器要避免任何对该对象的优化行为。(A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.)
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile int iNum = 10;
volatile 指出 iNum 是随时可能发生变化的,每次使用它的时候必须从原始内存地址中去读取,因而编译器生成的汇编代码会重新从iNum的原始内存地址中去读取数据。而不是只要编译器发现iNum的值没有发生变化,就只读取一次数据,并放入寄存器中,下次直接从寄存器中去取值,而不是重新从内存中去读取。
一般说来,volatile用在如下的三个地方:
中断服务程序中修改的供其它程序检测的变量需要加volatile;
多任务环境下各任务间共享的标志应该加volatile;
存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
volatile 指针
和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:
1. 修饰由指针指向的对象是const或volatile的:
const char* cptr;
volatile char* vptr;
2. 指针自身的值是const或volatile的:
char* const cptr;
char* volatile vptr;
可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
多线程下的volatile
当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。
---------------------
作者:friendbkf
来源:CSDN
原文:https://blog.csdn.net/friendbkf/article/details/45542337
版权声明:本文为博主原创文章,转载请附上博文链接!