通過反彙編分析C語言中volatile關鍵字的含義

根據C語言標準,volatile關鍵字的作用是禁止編譯器對相關變量的存取進行優化。本文利用VC 2010和GCC 4.4.7,分析volatile關鍵字對生成的彙編代碼的影響,以驗證volatile的具體含義。

VC 2010

以下是基礎C代碼

int gMark = 1;

int _tmain(int argc, _TCHAR* argv[])  
{  
	while(gMark){
	}
	return 0;
}  
以下是VC 2010 Releae編譯後的彙編代碼,可以看到循環直接被被優化掉了。

00A51000  jmp         wmain (0A51000h) 

如果把gMark聲明爲volatile int,再次VC 2010 Releae編譯,生成的彙編代碼如下,可以看到每次循環都要重新讀取一次gMark所在的內存。

01071000 mov         eax,dword ptr [gMark(1073018h)]

01071005 test        eax,eax 

01071007  jne         wmain (1071000h)

GCC 4.4.7

以下是基礎C代碼

int gMark = 1;
int main()
{
  while(gMark){
  }
  return 0;
}
以下是g++ -O3編譯後的反彙編代碼,可以看到循環直接被被優化掉了,與VC完全一樣。

Dump of assembler code forfunction main:

   0x0000000000400680 <+0>:  jmp   0x400680 <main>

Endof assembler dump.

如果把gMark聲明爲volatile int,g++ -O3編譯後的彙編代碼如下。可以看到每次循環都要重新讀取一次gMark所在的內存,這點與VC完全一致。

Dumpof assembler code for function main:

   0x0000000000400680 <+0>:  movl  $0x1,-0x4(%rsp)

   0x0000000000400688 <+8>:  nopl  0x0(%rax,%rax,1)

   0x0000000000400690 <+16>: mov   -0x4(%rsp),%eax

   0x0000000000400694 <+20>: test  %eax,%eax

   0x0000000000400696 <+22>: jne   0x400690 <main+16>

   0x0000000000400698 <+24>: repz retq

Endof assembler dump.

結論

如果不加volatile關鍵字,編譯器對C代碼中的多次讀取該變量的操作,有可能只生成一次加載內存到寄存器的指令。如果加上volatile,則每次C源碼中用到該變量,都需要從內存中重新加載。

其它

以上基本說明了volatile的含義。另外,如果C代碼的函數中有多處對volatile變量的讀寫,編譯器還必須在生成的彙編代碼中保留這些volatile變量的讀寫順序,見wiki(http://en.wikipedia.org/wiki/Memory_barrier)。

還有一個經常混淆的問題是,volatile是否還定義了原子語義?用volatile修飾變量,能不能保證對一個變量的讀寫操作不會被其他線程打斷?我們說根據C標準,volatile是沒有原子語義的。另外,剛纔生成的彙編代碼也說明了volatile變量的讀寫是沒有原子性的。

最後是volatile和memory barrier到底是什麼關係。我的理解是,volatile是對編譯器優化進行干預,memory barrier是對CPU的亂序執行進行干預,因此兩者沒有關係。但是有些編譯器對volatile變量賦予了acquire和release語義,從而使得volatile能有一定的memory barrier的作用。詳細見http://msdn.microsoft.com/zh-cn/library/ms686355。因爲這和編譯器以及編譯器的版本是密切相關的,對於開發應用程序的程序員,就當它不存在好了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章