C++中的关键字 volatile 详解

引子

突然想到一个解释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寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。







  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值