volatile和restrict这两个类型限定词的运用与编译器的优化存在着一定的关系。volatile这个关键字用在变量类型定义上,指明这个变量的值存在不确定因素。也就是说这个变量不光会被我们编写的程序改变值,也可能会被某个外部代理改变(比如:某个硬件中断、外部程序等)。这样就不能保证如果程序没有改变这个变量值,而又存在多次调用后进入寄存器中的值就一定正确。
从编译器的优化角度,举个例子:
int x=5;
int a, b;
a = x;
b = x;
由于程序没有更改X的值,但又存在多次调用,编译器为了优化运行速度,会给a赋值后,把X的值5从内存放入到寄存器中。当给b赋值时,不是再次读取X内存地址中的值,而是直接把寄存器中的5赋给b。这一优化对于普通变量没有问题。但如果定义成 volatile int x;则表明x可以被程序代码外的其他代理改变值。如果编译器也采用这样的优化,很可能在给b赋值时,x的值已经被程序外部的某个硬件中断改变了。这样从寄存器获取到的值肯定是不正确的。
因此当给变量加上volatile关键字,除了表示这一变量可以被其他代理改变值,也明确说明编译器不能为此变量进行上面那种方式的优化:每次调用这一变量,都从变量的地址中获取值,而不是寄存器(此变量使用的硬件内存地址是与其他并行运行的程序共享数据的,因此不管是程序自身改变变量值,还是其他代理改变变量值,都是改变内存地址中的数据)。
看个有趣的例子:
int square(volatile int *a)
{
return (*a * *a);
}
函数的目的本来是计算平方根,但由于a指针用了volatile关键字,两次获取a指针地址中的值不能完全保证一样,所以计算出来的结果也未必就是我们需要的。考虑修改成这样:
int square(volatile int *a)
{
int temp = *a;
return (temp * temp);
}
restrict关键字只能用来修饰指针,表示被定义的指针是访问指针中数据的唯一途径。这一目的是告诉编译器可以进行一些优化。看个例子:
int x = 2;
int *a = (int *) malloc(sizeof(int));
*a = 2;
int *b = &x;
*a += 2;
*b += 2;
x *= 3;
*a += 3;
*b += 3;
编译器进行优化时可以用一条语句代替:*a += 5;这对于a来说是正确的,但如果用*b += 5来优化b是不正确的。因为其他变量影响了结果。因此,当编译器不确定某些因素时,会放弃寻找某个途径进行优化。如果在变量前加上restrict关键字。则告诉编译器可以“放心大胆”的进行优化。但编译器并不会验证你定义为restrict的指针,是否真正是某个数据的唯一访问途径;就像数组的下标越界一样,如果你不遵守规则,编译器并不会指出错误,但后果由你自己负责:)
同样看个有趣的类子:
void change_array(restrict int *array, const restrict int *value,const int size)
{
for(int i=0;i<size;i++)
{
array[i] += *value;
}
}
int main(void)
{
int *array[SIZE] = {1,2,3};
change_array(array,&array[0],SIZE);
for(int i=0;i<SIZE;i++)
{
printf("%d \n",array[i]);
}
}
如果编译器支持优化,运行后的结果是:2 3 4 而不是实际正确的结果:2 4 5 。这是在定义函数时,指明两个指针为restrict,因此编译器进行优化了:在程序调用函数时,将value指针的变量值在寄存器中生成了一个副本。后面的执行都是获取寄存器上的value值。同时可以看出,当你没有遵守restrict定义的指针指向的变量只能通过该指针修改的规则时(函数中 value指针指向的数据,在main调用时,array指针也进行了修改),编译器不会检查。
对于优化来说,volatile是强制性,而restrict是建议性。也就是加了volatile则强制不进行优化,而加入restrict编译器也不一定肯定优化。大部分情况下restrict和什么都不加编译结果相同,restrict只是告诉编译器可以自由地做一些相关优化的假定。同时也告诉调用者仅使用满足restrict定义条件的参数,如果你不遵守,嘿嘿。。。
restrict这个关键字是C99标准加入,在C++中不支持,因此我在VC++中加入restrict关键字编译不了:(
关于restrict的加入,在网上还找到一段小故事:
为了提高 Cray机器上的效率, ANSI C委员会提出过一种称为noalias的机制来解决这个问题,用它来说明某个C指针可以认为是没有别名, 只是这种机制不成熟,这件事激怒了Dennis Ritchie,拿他对C的标准化过程做了唯一的一次干预。他写了一封公开信说“noalias必须靠边站,这一点是不能协商的。”
后来Cray的Mike Holly又抓起了这个难题,向数值C语言扩充工作组和C++委员会提出了一种改进的反别名建议。所建议的想法是允许程序员说明一个指针可以认为是没有别名的,采用的方式是将它说明为restrict。 这个建议C99采纳了,但标准C++拒绝了。
联系客服