volatile修饰符的作用

关于c中volatile关键字2007-12-21 23:01一个定义为volatile的变量是说这变量可能会被
意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在
用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的
备份。下面是volatile变量的几个例子:
    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量
    回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员
的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要
求volatile变量。不懂得volatile内容将会带来灾难。
    假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下
,看一下这家伙是不是直正懂得volatile完全的重要性。
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。
    2). 一个指针可以是volatile 吗?解释为什么。
    3). 下面的函数有什么错误:
         int square(volatile int *ptr)
         {
              return *ptr * *ptr;
         }
    下面是答案:
    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改
变。它是const因为程序不应该试图去修改它。
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个
buffer的指针时。
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是
,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
    int square(volatile int *ptr)
    {
         int a,b;
         a = *ptr;
         b = *ptr;
         return a * b;
     }
    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能
返不是你所期望的平方值!正确的代码如下:
     long square(volatile int *ptr)
     {
            int a;
            a = *ptr;
            return a * a;
     }
   
   
   
Volatile 关键字告诉编译器不要持有变量的临时性拷贝。一般用在多线程程序中,以避免
在其中一个线程操作该变量时,将其拷贝入寄存器。请看以下情形:

   A线程将变量复制入寄存器,然后进入循环,反复检测寄存器的值是否满足一定条件(它
期待B线程改变变量的值。
在此种情况下,当B线程改变了变量的值时,已改变的值对其在寄存器的值没有影响。所以
A线程进入死循环。
 
   volatile 就是在此种情况下使用。What does volatile do?

This is probably best explained by comparing the effects that volatile and
synchronized have on a method. volatile is a field modifier, while
synchronized modifies code blocks and methods. So we can specify three
variations of a simple accessor using those two keywords:

int i1;              int geti1() {return i1;}
volatile int i2;              int geti2() {return i2;}
         int i3; synchronized int geti3() {return i3;}
geti1() accesses the value currently stored in i1 in the current thread.
Threads can have local copies of variables, and the data does not have to be
the same as the data held in other threads. In particular, another thread may
have updated i1 in it's thread, but the value in the current thread could be
different from that updated value. In fact Java has the idea of a "main"
memory, and this is the memory that holds the current "correct" value for
variables. Threads can have their own copy of data for variables, and the
thread copy can be different from the "main" memory. So in fact, it is
possible for the "main" memory to have a value of 1 for i1, for thread1 to
have a value of 2 for i1 and for thread2 to have a value of 3 for i1 if thread
1 and thread2 have both updated i1 but those updated value has not yet been
propagated to "main" memory or other threads.

On the other hand, geti2() effectively accesses the value of i2 from "main"
memory. A volatile variable is not allowed to have a local copy of a variable
that is different from the value currently held in "main" memory. Effectively,
a variable declared volatile must have it's data synchronized across all
threads, so that whenever you access or update the variable in any thread, all
other threads immediately see the same value. Of course, it is likely that
volatile variables have a higher access and update overhead than "plain"
variables, since the reason threads can have their own copy of data is for
better efficiency.

Well if volatile already synchronizes data across threads, what is
synchronized for? Well there are two differences. Firstly synchronized obtains
and releases locks on monitors which can force only one thread at a time to
execute a code block, if both threads use the same monitor (effectively the
same object lock). That's the fairly well known aspect to synchronized. But
synchronized also synchronizes memory. In fact synchronized synchronizes the
whole of thread memory with "main" memory. So executing geti3() does the
following:

The thread acquires the lock on the monitor for object this (assuming the
monitor is unlocked, otherwise the thread waits until the monitor is unlocked)
.
The thread memory flushes all its variables, i.e. it has all of its variables
effectively read from "main" memory (JVMs can use dirty sets to optimize this
so that only "dirty" variables are flushed, but conceptually this is the same.
See section 17.9 of the Java language specification).
The code block is executed (in this case setting the return value to the
current value of i3, which may have just been reset from "main" memory).
(Any changes to variables would normally now be written out to "main" memory,
but for geti3() we have no changes.)
The thread releases the lock on the monitor for object this.
So where volatile only synchronizes the value of one variable between thread
memory and "main" memory, synchronized synchronizes the value of all variables
between thread memory and "main" memory, and locks and releases a monitor to
boot. Clearly synchronized is likely to have more overhead than volatile.

--------------------
                                                   c语言中volatile关键字的作用

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要
存 储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字
,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果 这个变量由别的程序
更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件
的触发,所以经常会写出这样的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
} 这段程序等待内存变量flag的值变为1(怀疑此处是0,有点疑问,)之后才运行do2()。变量
flag的值由别的程序更改,这个程序可能是某个硬件中 断服务程序。例如:如果某个按钮
按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够
得以继续运行。但是,编译器并 不知道flag的值会被别的程序修改,因此在它进行优化的
时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了
这样的 优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序
修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形 式:
volatile short flag;
需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之 后
就又不能正常运行了。因此经常会出现debug版本正常,但是release版本却不能正常的问
题。所以为了安全起见,只要是等待别的程序修改某个变量 的话,就加上volatile关键字


volatile的本意是“易变的”

由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如


static int i=0;

int main(void)
{
   ...
   while (1)
   {
      if (i) do_something();
   }
}

/* Interrupt service routine. */
void ISR_2(void)
{
   i=1;
}

   程序的本意是希望ISR_2中断 产生时,在main当中调用do_something函数,但是,由于
编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的 读操作
,然后每次if判断都只使用这个寄存器里面的“i副本”,导致do_something永远也不会被
调用。如果将将变量加上volatile修饰, 则编译器保证对此变量的读写操作都不会被优化
(肯定执行)。此例中i也应该如此说明。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同
意义;

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被
打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件
的良好设计了。

volatile 的含义
volatile总是与优化有关,编译器有一种技术叫做数 据流分析,分析程序中的变量在哪里
赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步
可以死代码消除。但有时这些优化不是 程序所需要的,这时可以用volatile关键字禁止做
这些优化,volatile的字面含义是易变的,它有下面的作用:

1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环
境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种
情况。

2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) ...

if的条件不会当作无条件真。

3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常
常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。

前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要L
OCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得
用别的方法,如atomic_inc。

对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用
那种复杂的形式,因为那样也不能保证原子性。

你可能不知道在Pentium及后续CPU中,下面两组指令

inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies

作用相同,但一条指令反而不如三条指令快。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值