C中的volatile关键字

来源:互联网 发布:java编程思想百度网盘 编辑:程序博客网 时间:2024/04/29 14:32

      就像const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.
例子1:
简单地说就是防止编译器对代码进行优化.比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(产生四条代码).

      例子2:

1
for(int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成
1
for(volatile int i=0; i<100000; i++);
它就会执行了
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
1
2
3
4
5
6
7
8
9
10
11
staticinti = 0;
int main(void)
{
//...
while(1)
{
if(i)dosomething();
}
}
/*Interruptserviceroutine.*/
voidISR_2(void){i=1;}
      程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
例子3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classGadget
{
    public:
        void Wait()
        {
            while(!flag_)
            {
                Sleep(1000);//sleeps for 1000milli seconds
            }
        }
        void Wakeup()
        {
            flag_=true;
        }
        //...
    private:
        bool flag_;
};
上面代码中Gadget::Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。至少这是程序作者的意图,然而,这个Wait函数是错误的。
假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:当你调用了某个Gadget的Wait函数后,即使另一个线程调用了Wakeup,Wait还是会一直循环下去。这是因为flag_的改变没有反映到缓存它的寄存器中去。
例子4:
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。

1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:
1
2
3
4
int square(volatile int *ptr)
{
    return ((*ptr) * (*ptr));
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
       关于Const:
      如果const int Max=100,那么 Max++会产生错误,因为用const修饰的变量被当作常量(并非肯定是个常量)使用,在同一个程序中不能对其进行赋值。但是const修饰的变量不允许这里修改不代表不允许别处修改,比如下代码:

int i = 5;
const int* p = &i;
*p = 6; // 不可以;
i = 7; // 完全可以,而且那个“const”的“*p”也跟着变成了7。
      另外,

对于非指针非引用的变量,const volatile同时修饰的意义确实不大。个人觉得。
2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
3). 这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
1
2
3
4
5
6
7
int square(volatile int *ptr)
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a*b;
}
由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:
1
2
3
4
5
6
long square(volatile int*ptr)
{
    int a;
    a = *ptr;
    return a*a;
}

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器);存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables),即中断服务程序中修改的供其它程序检测的变量需要加volatile;
3)多任务环境下各任务间共享的标志应该加volatile,如多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在2中可以通过关中断来实现,3中可以禁止任务调度(加锁等),1中则只能依靠硬件的良好设计了。

0 0