volatile表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类 型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员 都会被视为volatile.
该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。
简单示例:
DWORD __stdcall threadFunc(LPVOID signal)
{
int intSignal=reinterdivt_cast(signal);
intSignal=2;
while(intSignal!=1)
sleep(1000);
return 0;
}
该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。C 编译器是没有线程概念的,这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时 候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
label:
mov ax,signal
if(ax!=1)
goto label
注意:一个参数既可以是const同时是volatile,是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
3.restrict
关键字restrict通过允许编译器优化某几种代码增强了计算支持。记住,它只能用于指针,并且表明指针是访问一个数据对象的唯一且初始的方式。为了清楚为何这样做,我们需要看一些例子:
intar[10];
int restrict restar = (int)malloc(10sizeof(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两次访问数据之间,ar改变了该数据的值。没有关键字restrict,编译器将不得不设想比较糟糕的那一种形式,而使用之后,编译器可以放心大胆的寻找计算的捷径。可以将关键字作为指针型函数参量的限定词使用,这意味着编译器可以假定在函数体内没有其他标志符修改指针指向的数据,因而可以试着优化代码,反之不然。来看一下C99标准下C库中的两个函数,他们从一个位置把字节复制到另一个位置
voidmemcpy(void restrict s1,const void restrict s2,size_t n);
voidmemmove(void s1,const void s2,size_t);
memcpy要求两个指针的位置不能重叠,但memmove没有这个要求。把s1,s2声明为restrict意味着每个指针都是相应数据的唯一访问方式,因此他们不能访问同一数据块。这满足了不能有重叠的要求。
关键字restrict有两个读者:编译器,它告诉编译器可以自由地做一些优化的假定。另一个读者是用户,他告诉用户仅使用满足restrict要求的参数。一般,编译器没法检查你是否遵循了这一限制,如果你蔑视它,也就是让自己冒险。
原文地址:https://blog.51cto.com/14355585/2410707