Chinaunix首页 | 论坛 | 博客
  • 博客访问: 150288
  • 博文数量: 21
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 355
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-12 17:44
文章分类

全部博文(21)

文章存档

2011年(1)

2010年(5)

2009年(8)

2008年(7)

我的朋友

分类: C/C++

2010-05-11 15:47:41

C程序员经常认为volatile是那些可能被当前线程以外的线程所改变的变量,因此它们经常被认为是一种简单的原子操作的变量,但其实它们不是。使用volatile的目的仅在于抑制那些违背程序员意图的编译器的优化。看一段DSP上的程序:
 

short flag;
void test()
{
  do1();
  while(flag==0);
  do2();
}


这段程序等待内存变量flag的值变为1之后才运行do2()。

变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够得以继续运行。

但是,编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。

为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:

volatile short flag;

但是linux内核并不支持使用volatile关键字。因为共享数据即使使用了volatile关键字,仍然需要使用锁机制,当正确的使用了锁机制后,是不需要使用volatile关键字的。

现代的高性能编译器在目标码优化上都具备对指令进行乱序优化的能力。并且可以对访存的指令进行进一步的乱序,减少逻辑上不必要的访存,以及尽量提高 Cache命中率和CPU的LSU(load/store unit)的工作效率。所以在打开编译器优化以后,看到生成的汇编码并不严格按照代码的逻辑顺序是正常的。
绝大多数的编译器,通常不会优化掉对volatile对象的访问,并且通常保持同一个volatile对象的一系列读写操作是有序的(但是不能保证不同的volatile对象之间有序)。
但是,这不是绝对的。因为ANSI C99标准关于对volatile对象访问时编译器是否要绝对保证禁止乱序(reorder)和禁止访问合并(combine access)并没有做任何规定!仅仅是鼓励编译器最好不要去优化对volatile对象的访问,而唯一的强制要求仅仅是要求编译器保证对 volatile对象的访问优化不会跨越“sequence point”即可(所谓sequence point是指一些诸如外部函数调用、条件或循环跳转等关键点,具体定义请查阅C99标准内的详细说明)。
这就是说,如果一个编译器在两个sequence point之间像对待普通变量一样去优化volatile变量,也是完全符合C99标准的!比如:

volatile int a;

if (...) { ... } // sequence point

a = 1;
a = 2;
a = 3;
printk("...");

在两个sequence point之间,要是有编译器对a的赋值操作合并(即仅写入3)或者乱序(如写1和写2对调),都是完全符合C99标准的。所以,我们在使用的时候,不能指望用了volatile以后绝对能生成有序的完整的汇编码,即不要指望volatile来保证访存有序。实质上 volatile最大的作用主要还是在保证每次使用从内存中取值,而并不能保证编译器不做其他任何优化(毕竟volatile从字面上看意思是“易变”而不是“有序”。编译器只保证对volatile对象即时更新但不保证访问有序也不是说不过去的)。
阅读(1338) | 评论(0) | 转发(2) |
0

上一篇:转载-时间

下一篇:内存屏障是什么

给主人留下些什么吧!~~