天天看點

C99中的restrict和C89的volatile關鍵字

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()的反彙編如下圖:

C99中的restrict和C89的volatile關鍵字

我們把foo()改為:

int foo(int *restrict a,int *restrict b)
{
	*a = 1;
	*b = 2;
	return *a;
}
           

依然O2編譯:

C99中的restrict和C89的volatile關鍵字

可以清楚地看到,編譯器對傳回值做了優化處理.

之是以前一個例子,傳回值要從記憶體中擷取,是因為編譯器不确定指針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才對,然而:

C99中的restrict和C89的volatile關鍵字

程式得到了錯誤的結果。

是以,正如李林老師在《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選項:

C99中的restrict和C89的volatile關鍵字

注意圈出來的那一行,這是一個死循環,也就是說,flag < 2 這個條件被編譯器認為是永真!是以還是這句話,優化需謹慎!

而加上關鍵字之後:

static volatile int flag;
           
C99中的restrict和C89的volatile關鍵字

注意圈出來的那一行: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