Q&A about volatile & const(Z)

   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();
    }
}

这个强制转换告诉编译器,这个值不会被改变的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值