2011年2月5日星期六

<转>volatile关键字

  可能很多人都没用过C/C++中的这个关键词,甚至不知道它的存在,本人以前也只是有所耳闻,但似懂非懂。
  这是一个类型修饰符,位置同conststatic等。一个使用volatile修饰的变量,比如volatile int i; 每次对该变量的直接引用,都会访问内存,而不是从寄存器中读取(如果其已经在寄存器中)。这样一来,volatile似乎没什么用处,反倒会使数据的读取相对变慢很多。但是,如果没有volatile,编译器可能会优化你的程序,使得数据从寄存器中读取,从而加快程序的运行,但如果这个变量是同其它进程/线程共享的,就可能造成数据的不一致。多线程情况下,你可以使用互斥机制来保证对共享数据访问的原子性。但是,在单片机等嵌入式环境中,硬件经常不会有这种互斥机制的支持,这时某些共享的数据(比如端口)就可能会产生不一致的情况。而使用volatile就会使编译器不对代码进行优化,每次对该变量的访问都会从内存中读取。
  下面通过观察使用volatile前后编译产生的汇编代码的不同,来加深对volatile关键词的理解。
  对于下面的C代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int foo(void)
{
    int i = 0;
    while(i++ < 20);
    return i;
}
int
main(int argc, char **argv)
{
    int i = foo();
    int j = i;
    int k = i;
    printf("%d, %d, %d\n", i, j, k);
    return 0;
}
  代码中,使用foo()的返回值(20)来初始化整型变量i。之所以不直接赋值,是为了防止编译器优化,下面对j,k的赋值将直接使用20,而不是i,也不是寄存器。使用下面的命令编译:
1
$ gcc -S -O1 main.c # 优化等级1
  产生的汇编代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ... ]
.LC0:
 .string "%d, %d, %d\n"
[ ... ]
main:
[ ... ]
 call foo # 调用函数foo,返回值保存至寄存器eax
 movl %eax, 16(%esp) # 参数k入栈
 movl %eax, 12(%esp) # 参数j入栈
 movl %eax, 8(%esp) # 参数i入栈
 movl $.LC0, 4(%esp) # 格式字符串地址入栈
 movl $1, (%esp)
 call __printf_chk
 movl $0, %eax
 leave
 ret
[ ... ]
  可以看到,对j,k的赋值都是由寄存器eax进行的。下面把代码中的int i = foo();换成volatile int i = foo();,编译得到的汇编代码是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ... ]
main:
[ ... ]
 call foo # 调用函数foo,返回值保存至寄存器eax
 movl %eax, 44(%esp) # 为i赋值
 movl 44(%esp), %edx # 读取i值
 movl 44(%esp), %ecx # 读取i值
 movl 44(%esp), %eax # 读取i值
 movl %ecx, 16(%esp) # 参数k入栈,使用i值
 movl %edx, 12(%esp) # 参数j入栈,使用i值
 movl %eax, 8(%esp) # 参数i入栈
 movl $.LC0, 4(%esp) # 格式字符串地址入栈
 call __printf_chk
 movl $0, %eax
 leave
 ret
[ ... ]
  可以看到,每次对i的访问都是从内存(栈)中读取的。

volatile vs. const

  正像上面所描述的那样,volatile的意思是“易变的”C/C++中还有另外一个关键词叫const,用来限定变量不可被改变。似乎volatile和const意思相反,水火不容。但volatile const int N = 0;这样的语句是允许存在的。为什么呢?其实,这个语句可以这样来理解:变量N在本程序(或者设备)中是不可(禁止)改变的,但它可能会被其它程序(或者设备)改变。你或许知道,const只是编译器的限定,而没有执行期的检查,因此const也只是限于单个进程,因此volatileconst这两个关键词并不冲突。
  另外,普通的整型const变量,比如const int N = 10; int a[N]; 中,变量N可能并不会被分配内存(除非程序里面有显式使用N地址的表达式)。但是,根据volatile变量的特性,volatile const int N = 10;中变量N是一定会占用内存的。这一点,亦可以通过观察相应的汇编代码来验证。

没有评论:

发表评论