gcc局部變數不用初始化么?

#include&

void foo(void)
{
int i;
printf("%d
",i);
i = 999;
}

int main()
{
foo();
//printf("hello
");
foo();

return 0;
}

網上刷題的時候,遇到一道某通信公司的筆試題,問的是「運行結果是什麼?為什麼?如果在兩個foo()調用之間加printf("hello
")結果是什麼?」

心想這不是局部變數未初始化么,那不是編譯的時候就報錯了。可是我發現VS2013確實報錯了。但是在linux gcc下,好像又不報錯了。輸出也很有規律,並不是局部變數隨機值。

請問各位大神這是為什麼呢?這道題想考察什麼呢?


這題就是考官在賣弄所謂的「底層知識」,然而和譚浩強考 i+++i+++++i 的值一樣,其實是自取其辱。

以 C99 標準為例:

6.7.8/10 未顯式初始化的非靜態局部變數的值是「不定的」。

3.17.2 「不定的值」是「未指定的值」或「陷阱標識」。

3.17.3 「未指定的值」是該類型有效值,但本國際標準對於具體選擇哪個值不作要求。

6.2.6.1/5 對值為「陷阱標識」的對象進行讀操作是未定義行為。

所以,這種代碼有可能觸發未定義行為,能不能跑全靠運氣。即便不觸發,取值是多少也沒有標準保障。


然而,這在不同編譯器,不同編譯選項下的結果都是不一樣的。有些編譯器會保護性地加未使用內存初始化,不同編譯選項,可能檢測的方法還不一樣,那麼填充的內容也就不一樣。

特別是有printf之後。我猜面試官是想問i會被初始化為前4個字元?然而不同編譯器和編譯選項,編譯器的棧變數對齊規則也不一樣,這還不算有些編譯器有時候會插入一些額外的數據。


考官該死


gcc不報錯僅僅是因為語義並沒有錯誤,但它肯定會報warning。printf輸出並不能確定,因為局部變數i很可能優化為寄存器變數。如果完全不開優化,i會在棧上,連調2次foo第二次輸出999。如果中間加了printf,那輸出的結果就一定不是999了。


1:局部變數不初始化,在編譯時絕對不報錯,頂多報個warning。

2:局部變數不初始化,運行結果是不確定的。但是這個「不確定」,並不是指在任何情況下都不確定。事實上在某些情況下是可能有很大概率出現某些確定情況的(例如你這例子)。

3:再解釋一下你這例子:

首先,第一次調用foo,如果沒有其他約束條件,i的值確實是不確定的。但是,如果有其他約束條件,也未必不確定(例如說vc在debug模式下,會把未使用內存初始化為某些確定的值,這時的i可能就是確定的)。

然後,第二次調用foo,i的值很可能是確定的。因為第一次foo的末尾把i對應的內存設為了999。因為在這例子里,第二次調用foo時,兩次調用的棧頂很可能保持一致,所以兩個i是很可能指向同一塊內存的。而如果這兩次調用中間,沒有任何機制破壞這塊內存的數值的話,那第二次調用foo的i,很可能就保留了999這個數值。

事實上關於這個特性,還有一段有趣的bug:

記不清是openssl還是某個加密庫,有一個函數,用的就是這個特性:把一個未初始化的內存的值當做隨機數來使用。

然後,某次一個debian的維護者在review這段代碼的時候,發現這段內存居然沒初始化就用了,於是加了兩行代碼給它初始化了一下。結果就是本來應該是隨機數的輸出就不再隨機了---一個和安全相關的bug就這麼弄出來了。

所以這種東西是差不多類似於「茴字的第五種寫法」,拿來做面試題可以,但是誰要是真的用來寫代碼的話,後果自負。


發現好多人在噴考官,說不定參考答案就是結果未定義呢


猛然發現 g++ 02優化 一定是0啊...


未初始化不是應該是垃圾值嗎?


實踐中如果發現有這樣寫代碼的,立刻要拖出去亂棍打死。


未定義行為!(這是通識)

要解釋這種未定義行為產生的結果,需要懂該行為所涉及的編譯器以及操作系統相關的知識。

需要考慮:

編譯器怎麼處理這種未定義行為?(直接忽視還是自動初始化為0?還是其他?)

操作系統分配內存時,對分配的內存清零還是不作任何處理?

總之,它和特定的編譯器及特定的操作系統有關。


宋勁杉的《一站式學習C編程》一書41頁上的一道常式


推薦閱讀:

對於同樣的 C 語言代碼,為什麼 Mac OS X 上用 gcc 編譯運行的結果和其他系統不同?
GCC的參數-march=native是如何獲取cpu類型和指令集的?
在使用coroutine+asio多線程框架的時候,如何維護連接池復用連接?

TAG:Linux | C編程語言 | CC | GCC |