1、restrict
它隻可以用于限定指針,告知編譯器該指針是通路一個資料對象的唯一且初始的方式。即不存在其它進行修改操作的途徑。
主要作用是可以讓編譯器進行一些優化,生成更高效的目标代碼。
看個例子:
int foo(int *a,int *b)
{
*a = 1;
*b = 2;
return *a;
}
int main()
{
int *p, *q, ret;
ret = foo(p, q);
return 0;
}
我們用gcc -O2 -std=c99選項進行編譯,foo()的反彙編如下圖:
我們把foo()改為:
int foo(int *restrict a,int *restrict b)
{
*a = 1;
*b = 2;
return *a;
}
依然O2編譯:
可以清楚地看到,編譯器對傳回值做了優化處理.
之是以前一個例子,傳回值要從記憶體中擷取,是因為編譯器不确定指針a是不是唯一的一個可以通路到那片記憶體的指針,又可能還有其他指針通路或修改了那片記憶體。是以,即使是O2優化,傳回值還是從記憶體中得來的,編譯器是要保證百分之百的正确。
後面一個例子,明确告知了編譯器,a是唯一通路到a所指向的記憶體的指針,是以編譯器可以放心大膽地直接向寄存器寫傳回值。
然而,标準裡還有這樣的話:
If the declaration of intent is not followed and the object is accessed by an independent pointer, this will result in undefined behavior
我們把main函數改成這樣:
int foo(int *restrict a,int *restrict b)
{
*a = 1;
*b = 2;
return *a;
}
int main()
{
int *p, *q, ret;
ret = foo(p, p);
return 0;
}
這樣,按照本意,ret的值應該為2才對,然而:
程式得到了錯誤的結果。
是以,正如李林老師在《Linux環境進階程式設計》裡指出:restrict的限制(隻能通過該指針通路),是由程式員來保證的,編譯器并不能完全保證。[1]
再如:
int foo(int *restrict a,int *b)
{
*a = 1;
*b = 2;
return *a;
}
在我的機器上反彙編,發現O1未做處理,但O2時做了優化處理。
未做優化的原因,其實是編譯器對指針b的顧慮:萬一b也指向的空間和a一樣呢?
是以,優化需謹慎
2、volatile
與restrict讓編譯器優化相反,volatile是阻止編譯器優化。簡單地說,volatile告訴編譯器該被變量除了可被程式修改外,還可能被其他代理、線程修改。是以,當使用volatile 聲明的變量的值的時候,系統總是重新從它所在的記憶體讀取資料,而不使用寄存器中的緩存的值。
繼續看個例子:
static int flag;
void foo()
{
flag = 0;
while (flag < 2)
;
}
int main()
{
foo();
return 0;
}
依然用-O2選項:
注意圈出來的那一行,這是一個死循環,也就是說,flag < 2 這個條件被編譯器認為是永真!是以還是這句話,優化需謹慎!
而加上關鍵字之後:
static volatile int flag;
注意圈出來的那一行:flag的擷取是從記憶體中來的。
這個例子是為了說明volatile能阻止編譯器做常量合并、常量傳播等優化。
其他的方面的作用可以參考 [2]
一般說來,volatile用在如下的幾個地方:
1、中斷服務程式中修改的供其它程式檢測的變量需要加volatile;
2、多任務環境下各任務間共享的标志應該加volatile;
3、存儲器映射的硬體寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
然而沖突的是:根據相關的标準(C,C++,POSIX,WIN32)和目前絕大多數實作,對volatile變量的操作并不是原子的,也不能用來為線程建立嚴格的happens-before關系。[3]
參考資料:
[1]:李林《linux程式設計實踐》
[2]:詳解volatile
[3]:volatile wiki