Q:
a) What does the keyword volatile mean? Give an example
b) Can a parameter be both const and volatile? Give an example
c) Can a pointer be volatile? Give an example
A:
a、volatile的本意是“易变的”, 就是该变量会以编译器无法预知的方式发生变化,请编译器不要做优化(所有的编译器的优化均假设编译器知道变量的变化规律)
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
b、一个参数可以同时兼备volatile和const属性。
c、可以。
看下面的说明:
你真的了解const 和 volatile吗?
const是C++中最常用的几个关键字之一,对确保程序的完整性有着重要的意义。它在调用者和被调用者之间传递这样一种信息:我不会改变你的,放心吧。volatile在C++中是一个不常用的关键字,但是每个学过的人也总会知道volatile表示这个变量的值可能在程序的外部被改变。
我们先来看看几段程序(可能会比较长,可以和后面的解释一起看):
0000 // 第一段
0001 DWORD __stdcall threadFunc(LPVOID signal)
0002 {
0003 int* intSignal=reinterpret_cast<int*>(signal);
0004 *intSignal=2;
0005 while(*intSignal!=1)
0006 0;
0007 cerr<<"ended"<<endl;
0008 return 0;
0009 }
0010
0011 void volatileTest(DWORD (__stdcall* threadFunc)(LPVOID))
0012 {
0013 int signal=1;
0014 HANDLE h=::CreateThread(NULL,0,threadFunc,(LPVOID)&signal,0,NULL);
0015 Sleep(50);
0016 while(signal!=2)
0017 {
0018 0;
0019 }
0020 cerr<<"hi, stopppppppp!!!!"<<endl;// if move down?
0021 signal=1;
0022 ::WaitForSingleObject(h,INFINITE);
0023 ::CloseHandle(h);
0024 }
0025
0026 // 第二段
0027 class constTest
0028 {
0029 int mIntValue;
0030 int* mIntPointer;
0031 int& mIntRef;
0032 public:
0033 constTest():mIntPointer(&mIntValue),mIntRef(mIntValue)
0034 {}
0035 int* getIntPointer() const
0036 {
0037 return mIntPointer;
0038 }
0039 int& getIntRef() const
0040 {
0041 return mIntRef;
0042 }
0043 int& getIntValue() const
0044 {
0045 return mIntValue;
0046 }
0047 };
0048
0049 // 第三段
0050 char func(const int i, const char* str)
0051 {
0052 return str[i];
0053 }
先看第一段程序,逻辑很简单。由volatileTest创建线程,执行threadFunc。因为signal本身等于1,主线程将等待其值变为2,然后将其值设回1;这时候,threadFunc结束;随之主线程结束。可是,如果你在release模式下运行这个程序,一般来说(各种编译器优化程度不一样。)死循环。为什么呢?因为,在volatileTest中,它是把signal的值放在一个寄存器中,然后不停的测试这个寄存器的值。就象下面这段伪码一样。
mov ax,signal
label:
if(ax!=1)
goto label
因为对于C++编译器来说,它不知道这个值会被其他线程所修改。自然就把它cache在寄存器里面。记住,C++编译器是没有线程概念的!!!这时候就需要用到volatile。volatile的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在volatileTest中的signal和threadFunc中的intSignal前面加上volatile关键字,这时候,编译器所作的循环变为如下面伪码所示:
label:
mov ax,signal
if(ax!=1)
goto label
好,第一个例子先在此告一段落,我们看第二个例子。这个例子是一个简单的类,为什么mIntPointer和mIntRef可以被const函数引出呢?const函数和非const函数最本质的区别是:constTest的const函数中的this指针是const constTest*类型的,而普通函数里的this指针是constTest*类型的。也就是说,当你调用const函数的时候,只要保证它的成员变量不被改变,就不会出现语法错误。我们来看一下这三个函数。
第一个函数的返回值是对象内部的一个指针(当然,编译器并不知道它是指向这个类的另一个成员的,这对编译器来说没有区别)。调用者可以改变这个指针指向的值,但是这不重要,因为const关心的是:这个指针本身不会被改变。
第二个函数的返回值没问题,但是咋一看好像前一句有问题。因为这里很明显的把一个类成员付给了这个引用。C++编译器没有那么聪明,它只会一句一句的分析,这两句独立的看都没问题,所以就通过了。
第三个函数会引起一个编译错误。因为编译器明白的知道这个值会被改变。
说得更透彻一点,第一个函数的返回是做了一个从int* const到int*的隐式转换。第三个函数的返回做的是从const int到int&的转换,这不是隐式的!!!
说到int* const,我们可以说一下它和const int*(也就是int const*)的区别。int* const p;表示p这个指针指向的值可以被修改,但是这个指针本身不可以被修改。也就是说,*p=0;是合法的,但是p=NULL是非法的。const int*和int const*正好相反。如果你要声明一个指针本身不能被修改,其指向的值也不能被修改,就要写成int const* const p;
这里有一个很有趣的现象就是,如果你定义了一个typedef如下:typedef int* int_pointer;那么int_pointer const p;和int const* p;是等价的,而并不是想象中的int* const p;为什么呢?自己想一下(提示,和GP有关的。)
说到了这里,我们看第三段程序。这段程序没什么错误,但是在前后一致性上有问题。因为事实上,int前面的const是多余的,因为调用者并不担心你会改变一个传值参数。如果说加上这个const是为了不让自己在函数里面不小心改变它的值,那么为什么不把后面的const char*也写成const char* const呢?
说了那么长,我们再说一些写程序中和const和volatile相关的内容:
1.如果你在类中间声明了一个值,但是又想在const成员函数中改变它,可以在前面加mutable。譬如下面这个正方形类,假设计算面积是一个费时的操作,你可以在需要的时候再计算它,也可以把结果存下来。
0000 class Rect
0001 {
0002 int mBorderSize;
0003 mutable int mArea;
0004 public:
0005 Rect(int borderSize):mBorderSize(borderSize),mArea(-1)
0006 {
0007 }
0008 void setBorderSize(int borderSize)
0009 {
0010 mBorderSize=borderSize;
0011 mArea=-1;
0012 }
0013 int getArea() const
0014 {
0015 if(mArea==-1)
0016 {
0017 mArea=mBorderSize*mBorderSize;
0018 }
0019 return mArea;
0020 }
0021 };
2.const的构造函数是不存在的。因为任何const的对象总还是需要有个初值的。
3.const只是表示不能被显式的语句所改变,并不是说被标为const的值真的不会改变。具体请参见相关代码的sumUp函数。
这个特性曾经一度导致了C++(C也一样)不能对一些数学运算尤其是大型向量运算做优化。不过随着C99中restricted和C++ STL中valarray的引入将会得到很大的改善。
4.最重要的总是在最后。当你写多线程程序的时候,不可避免地要用到volatile(如果你还没用到,而且又没出什么事,是因为你运气太好了,或者是因为你的编译器太烂了。)。如果有这样一段代码:
volatile int* signal=...
for(;;)
{
if(*signal!=1)
Sleep(100);
else
{
lock();
// do a lot of work with *signal
unlock();
}
}
在这里所有对*signal的访问都是先从内存取出,使用完了放回去,然后再取出,再使用,...但是,在lock和unlock之间,如果你可以确定signal的值不被其它线程改变的话(这很普遍),你完全可以把它cache在寄存器里面,这可以快很多。
这种情况可以这样解决:
volatile int* signal=...
for(;;)
{
if(*signal!=1)
Sleep(100);
else
{
lock();
int* fastSignal=const_cast<int*>(signal);
// do a lot of work with *fastSignal
unlock();
}
}
这个强制转换告诉编译器,这个值不会被改变的。