12.5.2 volatile类型限定符
volatile 限定符告知计算机,代理(⽽不是变量所在的程序)可以
改变该变量的值。通常,它被⽤于硬件地址以及在其他程序或同时运
⾏的线程中共享数据。例如,⼀个地址上可能储存着当前的时钟时
间,⽆论程序做什么,地址上的值都随时间的变化⽽改变。或者⼀个
地址⽤于接受另⼀台计算机传⼊的信息。
volatile的语法和const⼀样:
olatile int loc1;/* loc1 是⼀个易变的位置 */
volatile int * ploc; /* ploc 是⼀个指向易变的位置的指针 */
以上代码把loc1声明为volatile变量,把ploc声明为指向volatile变量
的指针。
读者可能认为volatile是个可有可⽆的概念,为何ANSI 委员把
volatile关键字放⼊标准?原因是它涉及编译器的优化。例如,假设有
下⾯的代码:
vall =x;
/* ⼀些不使⽤ x 的代码*/
val2 = x
智能的(进⾏优化的)编译器会注意到以上代码使⽤了两次 x,但
并未改变它的值。于是编译器把 x的值临时储存在寄存器中,然后在
val2需要使⽤x时,才从寄存器中(⽽不是从原始内存位置上)读取x的
值,以节约时间。这个过程被称为⾼速缓存(caching)。通常,⾼速
缓存是个不错的优化⽅案,但是如果⼀些其他代理在以上两条语句之
间改变了x的值,就不能这样优化了。如果没有volatile关键字,编译器
就不知道这种事情是否会发⽣。因此,为安全起⻅,编译器不会进⾏
⾼速缓存。这是在 ANSI 之前的情况。现在,如果声明中没有volatile
关键字,编译器会假定变量的值在使⽤过程中不变,然后再尝试优化
代码。
可以同时⽤const和volatile限定⼀个值。例如,通常⽤const把硬件
时钟设置为程序不能更改的变量,但是可以通过代理改变,这时⽤
volatile。只能在声明中同时使⽤这两个限定符,它们的顺序不重要,
如下所⽰:
volatile const int loc;
const volatile int * ploc;
12.5.3 restrict类型限定符
restrict 关键字允许编译器优化某部分代码以更好地⽀持计算。它
只能⽤于指针,表明该指针是访问数据对象的唯⼀且初始的⽅式。要
弄明⽩为什么这样做有⽤,先看⼏个例⼦。考虑下⾯的代码:
int ar[10];
int * restrict restar = (int *) malloc(10 * sizeof(int));
int * par = ar;
这⾥,指针restar是访问由malloc()所分配内存的唯⼀且初始的⽅
式。因此,可以⽤restrict关键字限定它。⽽指针par既不是访问ar数组
中数据的初始⽅式,也不是唯⼀⽅式。所以不⽤把它设置为restrict。
现在考虑下⾯稍复杂的例⼦,其中n是int类型:
for (n = 0; n < 10; n++)
{
par[n] += 5;
restar[n] += 5;
ar[n] *= 2;
par[n] += 3;
restar[n] += 3;
}
由于之前声明了 restar 是访问它所指向的数据块的唯⼀且初始的⽅
式,编译器可以把涉及 restar的两条语句替换成下⾯这条语句,效果相
同:
restar[n] += 8; /* 可以进⾏替换 */
但是,如果把与par相关的两条语句替换成下⾯的语句,将导致计
算错误:
par[n] += 8; / * 给出错误的结果 */
这是因为for循环在par两次访问相同的数据之间,⽤ar改变了该数
据的值。
在本例中,如果未使⽤restrict关键字,编译器就必须假设最坏的
情况(即,在两次使⽤指针之间,其他的标识符可能已经改变了数
据)。如果⽤了restrict关键字,编译器就可以选择捷径优化计算。
restrict 限定符还可⽤于函数形参中的指针。这意味着编译器可以
假定在函数体内其他标识符不会修改该指针指向的数据,⽽且编译器
可以尝试对其优化,使其不做别的⽤途。例如,C 库有两个函数⽤于
把⼀个位置上的字节拷⻉到另⼀个位置。在C99中,这两个函数的原型
是:
void * memcpy(void * restrict s1, const void * restrict s2, size_t n);
void * memmove(void * s1, const void * s2, size_t n);
这两个函数都从位置s2把n字节拷⻉到位置s1。memcpy()函数要求
两个位置不重叠,但是memove() 没有这样的要求。声明s1和s2为
restrict说明这两个指针都是访问相应数据的唯⼀⽅式,所以它们不能
访问相同块的数据。这满⾜了memcpy()⽆重叠的要求。memmove()函
数允许重叠,它在拷⻉数据时不得不更⼩⼼,以防在使⽤数据之前就
先覆盖了数据。
restrict 关键字有两个读者。⼀个是编译器,该关键字告知编译器
可以⾃由假定⼀些优化⽅案。另⼀个读者是⽤⼾,该关键字告知⽤⼾
要使⽤满⾜restrict要求的参数。总⽽⾔之,编译器不会检查⽤⼾是否
遵循这⼀限制,但是⽆视它后果⾃负。