volatile有好几个特性,让我们来验证一下
1.对声明为volatile的变量操作时,每次都会从内存中取值,而不会使用原先保存在寄存器中的值。
让我们看一下两个例子,一个例子是对不声明为volatile的变量操作,一个例子是对声明为volatile的变量操作。编译器为gcc version 4.8.4,平台为32位ubuntu14.04,开启了一级优化,即g++ -O1 -std=c++11。汇编部分只看有注释的部分就行了。
//非volatile版
#include<stdio.h>
#include<pthread.h>
#include<iostream>
int fun(int a){
int x;
scanf("%d",&x);
if(a > x)
return a;
else
return x;
}
int main(){
int a = 5; //声明为非volatile
int b = 10;
int c = 20;
int d;
scanf("%d",&c);
a = fun(c);
b = a + 1;
printf("%d,%d\n",b,d);
return 0;;
}
080485fd <_Z3funi>:
80485fd: 53 push %ebx
80485fe: 83 ec 28 sub $0x28,%esp
8048601: 8b 5c 24 30 mov 0x30(%esp),%ebx
8048605: 8d 44 24 1c lea 0x1c(%esp),%eax
8048609: 89 44 24 04 mov %eax,0x4(%esp)
804860d: c7 04 24 50 87 04 08 movl $0x8048750,(%esp)
8048614: e8 d7 fe ff ff call 80484f0 <scanf@plt>
8048619: 8b 44 24 1c mov 0x1c(%esp),%eax
804861d: 39 c3 cmp %eax,%ebx
804861f: 0f 4d c3 cmovge %ebx,%eax
8048622: 83 c4 28 add $0x28,%esp
8048625: 5b pop %ebx
8048626: c3 ret
08048627 <main>:
8048627: 55 push %ebp
8048628: 89 e5 mov %esp,%ebp
804862a: 83 e4 f0 and $0xfffffff0,%esp
804862d: 83 ec 20 sub $0x20,%esp
8048630: c7 44 24 1c 14 00 00 movl $0x14,0x1c(%esp)
8048637: 00
8048638: 8d 44 24 1c lea 0x1c(%esp),%eax
804863c: 89 44 24 04 mov %eax,0x4(%esp)
8048640: c7 04 24 50 87 04 08 movl $0x8048750,(%esp)
8048647: e8 a4 fe ff ff call 80484f0 <scanf@plt>
804864c: 8b 44 24 1c mov 0x1c(%esp),%eax
8048650: 89 04 24 mov %eax,(%esp)
8048653: e8 a5 ff ff ff call 80485fd <_Z3funi> //调用fun函数,返回值保存在eax中
8048658: c7 44 24 0c 00 00 00 movl $0x0,0xc(%esp)
804865f: 00
8048660: 83 c0 01 add $0x1,%eax //直接使用eax中的值(b = a + 1)
8048663: 89 44 24 08 mov %eax,0x8(%esp)
8048667: c7 44 24 04 53 87 04 movl $0x8048753,0x4(%esp)
804866e: 08
804866f: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048676: e8 35 fe ff ff call 80484b0 <__printf_chk@plt>
804867b: b8 00 00 00 00 mov $0x0,%eax
8048680: c9 leave
8048681: c3 ret
//volatile版本
#include<stdio.h>
#include<pthread.h>
#include<iostream>
int fun(int a){
int x;
scanf("%d",&x);
if(a > x)
return a;
else
return x;
}
int main(){
volatile int a = 5;//声明为volatile
int b = 10;
int c = 20;
int d;
scanf("%d",&c);
a = fun(c);
b = a + 1;
printf("%d,%d\n",b,d);
return 0;;
}
080485fd <_Z3funi>:
80485fd: 53 push %ebx
80485fe: 83 ec 28 sub $0x28,%esp
8048601: 8b 5c 24 30 mov 0x30(%esp),%ebx
8048605: 8d 44 24 1c lea 0x1c(%esp),%eax
8048609: 89 44 24 04 mov %eax,0x4(%esp)
804860d: c7 04 24 60 87 04 08 movl $0x8048760,(%esp)
8048614: e8 d7 fe ff ff call 80484f0 <scanf@plt>
8048619: 8b 44 24 1c mov 0x1c(%esp),%eax
804861d: 39 c3 cmp %eax,%ebx
804861f: 0f 4d c3 cmovge %ebx,%eax
8048622: 83 c4 28 add $0x28,%esp
8048625: 5b pop %ebx
8048626: c3 ret
08048627 <main>:
8048627: 55 push %ebp
8048628: 89 e5 mov %esp,%ebp
804862a: 83 e4 f0 and $0xfffffff0,%esp
804862d: 83 ec 20 sub $0x20,%esp
8048630: c7 44 24 18 05 00 00 movl $0x5,0x18(%esp)
8048637: 00
8048638: c7 44 24 1c 14 00 00 movl $0x14,0x1c(%esp)
804863f: 00
8048640: 8d 44 24 1c lea 0x1c(%esp),%eax
8048644: 89 44 24 04 mov %eax,0x4(%esp)
8048648: c7 04 24 60 87 04 08 movl $0x8048760,(%esp)
804864f: e8 9c fe ff ff call 80484f0 <scanf@plt>
8048654: 8b 44 24 1c mov 0x1c(%esp),%eax
8048658: 89 04 24 mov %eax,(%esp)
804865b: e8 9d ff ff ff call 80485fd <_Z3funi>//调用fun函数
8048660: 89 44 24 18 mov %eax,0x18(%esp) //把返回值放入a对应的内存中
8048664: 8b 44 24 18 mov 0x18(%esp),%eax //从a的内存中取值
8048668: c7 44 24 0c 00 00 00 movl $0x0,0xc(%esp)
804866f: 00
8048670: 83 c0 01 add $0x1,%eax //然后加1(b = a + 1)
8048673: 89 44 24 08 mov %eax,0x8(%esp)
8048677: c7 44 24 04 63 87 04 movl $0x8048763,0x4(%esp)
804867e: 08
804867f: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048686: e8 25 fe ff ff call 80484b0 <__printf_chk@plt>
804868b: b8 00 00 00 00 mov $0x0,%eax
8048690: c9 leave
8048691: c3 ret
2.编译器不会对声明为volatile的变量进行优化,比如说声明了一个变量但是没有使用或者是使用了但是程序不关心使用后的结果。如果是没有声明volatile的变量编译器会忽略掉这个变量,但是如果把这个变量声明为volatile,那么编译器就不可以忽略这个变量,看下面的例子。
#include<stdio.h>
#include<pthread.h>
#include<iostream>
int main(){
int a = 5;
int b = 10;
int c = 20;
int d;
a = b + 1;
return 0;;
}
生成的汇编代码中把所有的变量都忽略了
0804857d <main>:
804857d: b8 00 00 00 00 mov $0x0,%eax
8048582: c3 ret
接下来让我们把a声明为volatile变量再看看会发生什么事情
#include<stdio.h>
#include<pthread.h>
#include<iostream>
int main(){
volatile int a = 5;
int b = 10;
int c = 20;
int d;
return 0;;
}
生成的汇编中并没有忽略变量a
0804857d <main>:
804857d: 83 ec 10 sub $0x10,%esp
8048580: c7 44 24 0c 05 00 00 movl $0x5,0xc(%esp)// a = 5
8048587: 00
8048588: b8 00 00 00 00 mov $0x0,%eax
804858d: 83 c4 10 add $0x10,%esp
8048590: c3 ret
3.顺序性(和java中的顺序性不太一样)
这里用的g++的2级优化
int a;
int b;
int fun(){
a = b + 1;
b = 0;
return 0;
}
080485c0 <_Z3funv>:
80485c0: a1 2c a0 04 08 mov 0x804a02c,%eax //取b的值
80485c5: c7 05 2c a0 04 08 00 movl $0x0,0x804a02c // b = 0
80485cc: 00 00 00
80485cf: 83 c0 01 add $0x1,%eax // a = b + 1
80485d2: a3 30 a0 04 08 mov %eax,0x804a030
80485d7: 31 c0 xor %eax,%eax
80485d9: c3 ret
由上面的例子可见编译器会对原来的程序重排顺序,再让我们把a声明为volatile看还会发生重排吗
这里用的g++的2级优化
volatile int a;
int b;
int fun(){
a = b + 1;
b = 0;
return 0;
}
080485c0 <_Z3funv>:
80485c0: a1 2c a0 04 08 mov 0x804a02c,%eax //取b的值
80485c5: c7 05 2c a0 04 08 00 movl $0x0,0x804a02c //b = 0
80485cc: 00 00 00
80485cf: 83 c0 01 add $0x1,%eax // a = b + 1
80485d2: a3 30 a0 04 08 mov %eax,0x804a030
80485d7: 31 c0 xor %eax,%eax
80485d9: c3 ret
从上面的程序来看还是会发生指令重排,让我们再把两个变量都声明为volatile来看看。
这里用的g++的2级优化
volatile int a;
volatile int b;
int fun(){
a = b + 1;
b = 0;
return 0;
}
080485c0 <_Z3funv>:
80485c0: a1 2c a0 04 08 mov 0x804a02c,%eax //取b的值
80485c5: 83 c0 01 add $0x1,%eax // a = b+1
80485c8: a3 30 a0 04 08 mov %eax,0x804a030//把结果放入a中
80485cd: 31 c0 xor %eax,%eax
80485cf: c7 05 2c a0 04 08 00 movl $0x0,0x804a02c //b = 0
80485d6: 00 00 00
80485d9: c3 ret
从上面结果来看,如果两个都声明为volatile,那么编译器就不会对指令进行重排