C中的volatile关键字

      就像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中则只能依靠硬件的良好设计了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值