为什么是volatile

  在上篇博文《线程并发执行带来的问题》的评论中,有几个朋友(锦瑟无端五十弦Kevin-moon等)说到了要用volatile声明变量的问题,首先非常感谢他们的指点。以前一直只知道volatile关键字是通知编译器在对这个变量进行操作的时候,每次都从其内存中读取数据,不要对其进行优化,但对其具体的过程不了解。今天看一篇文章《汇编与C之间的关系》,恰好提到这个问题,于是就将其记录下来,还请您多多指教。

  首先,现看一个简单的程序,就是用tmp对buf数组进行赋值,程序很简单,所以就没加注释了:

 

1 #include<stdio.h>
2
3 unsigned char tmp;
4
5 unsigned char buf[3];
6
7  int main()
8 {
9 buf[0] = tmp;
10 buf[1] = tmp;
11 buf[2] = tmp;
12
13 return 0;
14 }
15  

首先对其进行编译:

 

huangwei@ubuntu:~/Desktop$ gcc volatile.c -o a.out -g

huangwei@ubuntu:~/Desktop$ objdump -dS a.out

 

1 buf[0] = tmp;
2   80483b7: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
3 80483be: a2 19 a0 04 08 mov %al,0x804a019
4 buf[1] = tmp;
5   80483c3: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
6 80483ca: a2 1a a0 04 08 mov %al,0x804a01a
7 buf[2] = tmp;
8   80483cf: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
9 80483d6: a2 1b a0 04 08 mov %al,0x804a01b
10  

上面生成的汇编代码我们可以看出,程序每次从0x804a018地址取出tmp的值,然后赋值给buf数组元素,这样的代码,显然符合我们的预期。接着,我们让编译器对其进行优化编译,我们再来看看其生成的汇编代码的区别:

 

huangwei@ubuntu:~/Desktop$ gcc volatile.c -o b.out -g -O

huangwei@ubuntu:~/Desktop$ objdump -dS b.out

 

1 buf[0] = tmp;
2   80483b7: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
3 80483be: a2 19 a0 04 08 mov %al,0x804a019
4 buf[1] = tmp;
5   80483c3: a2 1a a0 04 08 mov %al,0x804a01a
6 buf[2] = tmp;
7   80483c8: a2 1b a0 04 08 mov %al,0x804a01b
8  

跟上面的代码相比,可以发现,程序中只有第一次赋值的时候才从内存中取得tmp的值,而其他的时候,都是直接将寄存器al中保存的最开始的tmp的值赋给buf数组元素。这样在一般的情况下,是没有问题的,但是联系到线程并发执行的问题,我们再来仔细考虑一下这个问题。当该线程第一次从内存中取出tmp的值后,操作系统发生调度,调度另一个线程执行,而这个线程又对tmp的值进行了修改,接下来,当原始线程恢复执行后,其寄存器al中保存的仍然是tmp最初的值,其给tmp的赋值,仍是tmp最初的值。这样,就产生的数据不同步的问题。

 

  接下来,我们用volatile来声明tmp变量,其他的跟上面的程序一样:

 

  volatile unsigned char tmp;

 

我们再让编译器对其进行优化编译:

 

huangwei@ubuntu:~/Desktop$ gcc volatile.c -o c.out -g -O

huangwei@ubuntu:~/Desktop$ objdump -dS c.out

 

1 buf[0] = tmp;
2   80483b7: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
3 80483be: a2 19 a0 04 08 mov %al,0x804a019
4 buf[1] = tmp;
5   80483c3: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
6 80483ca: a2 1a a0 04 08 mov %al,0x804a01a
7 buf[2] = tmp;
8   80483cf: 0f b6 05 18 a0 04 08 movzbl 0x804a018,%eax
9 80483d6: a2 1b a0 04 08 mov %al,0x804a01b
10  

 

  从上面的汇编代码可以看出,现在程序每次都从内存中取出tmp的值,然后赋值给buf数组元素,这样的话,就符合预想的情况了。上面只是个人的一些想法,错误的地方,还请您斧正。谢谢啦。

 

 

 

 

 

posted @ 2010-06-02 16:25  MR_H  阅读(2604)  评论(28编辑  收藏  举报