簡體   English   中英

斯坦福大學教程和GCC之間的沖突

[英]Conflict between a Stanford tutorial and GCC

根據這部電影(大約38分鍾),如果我有兩個具有相同本地變量的函數,它們將使用相同的空間。 所以下面的程序,應該打印5 gcc結果編譯它-1218960859 為什么?

該程序:

#include <stdio.h>

void A()
{
    int a;
    printf("%i",a);
}

void B()
{
    int a;
    a = 5;
}

int main()
{
    B();
    A();
    return 0;
}

根據要求,這是反匯編程序的輸出:

0804840c <A>:
 804840c:   55                      push   ebp
 804840d:   89 e5                   mov    ebp,esp
 804840f:   83 ec 28                sub    esp,0x28
 8048412:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048415:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048419:   c7 04 24 e8 84 04 08    mov    DWORD PTR [esp],0x80484e8
 8048420:   e8 cb fe ff ff          call   80482f0 <printf@plt>
 8048425:   c9                      leave  
 8048426:   c3                      ret    

08048427 <B>:
 8048427:   55                      push   ebp
 8048428:   89 e5                   mov    ebp,esp
 804842a:   83 ec 10                sub    esp,0x10
 804842d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048434:   c9                      leave  
 8048435:   c3                      ret    

08048436 <main>:
 8048436:   55                      push   ebp
 8048437:   89 e5                   mov    ebp,esp
 8048439:   83 e4 f0                and    esp,0xfffffff0
 804843c:   e8 e6 ff ff ff          call   8048427 <B>
 8048441:   e8 c6 ff ff ff          call   804840c <A>
 8048446:   b8 00 00 00 00          mov    eax,0x0
 804844b:   c9                      leave  
 804844c:   c3                      ret    
 804844d:   66 90                   xchg   ax,ax
 804844f:   90                      nop

是的,是的,這是未定義的行為 ,因為您使用的是未初始化的變量1

但是,在x86架構2上此實驗應該可行 該值不會從堆棧中“擦除”,並且由於它未在B()初始化,因此如果堆棧幀相同,則仍應存在相同的值。

我斗膽猜測,由於int a不內所用void B()編譯器優化的代碼出來,和一個5永遠不會寫入到堆棧上的該位置。 嘗試在B()添加printf - 它可能會起作用。

此外,編譯器標志 - 即優化級別 - 也可能影響該實驗。 嘗試通過將-O0傳遞給gcc來禁用優化。

編輯:我剛用gcc -O0 (64位)編譯你的代碼,實際上,程序打印5,就像熟悉調用堆棧所期望的那樣。 實際上,即使沒有-O0也能工作。 32位版本可能表現不同。

免責聲明:不要永遠, 永遠使用這樣的“真正的”代碼!

1 - 關於這是否是正式的“UB”,或者只是不可預測, 下面正在進行辯論。

2 - 也是x64,可能是使用調用堆棧的每個其他架構(至少有一個MMU)


讓我們來看看一個原因, 沒有奏效。 這在32位中最好看,所以我將使用-m32編譯。

$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)

我用$ gcc -m32 -O0 test.c編譯(禁用了優化)。 當我運行它時,它打印垃圾。

看看$ objdump -Mintel -d ./a.out

080483ec <A>:
 80483ec:   55                      push   ebp
 80483ed:   89 e5                   mov    ebp,esp
 80483ef:   83 ec 28                sub    esp,0x28
 80483f2:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80483f5:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80483f9:   c7 04 24 c4 84 04 08    mov    DWORD PTR [esp],0x80484c4
 8048400:   e8 cb fe ff ff          call   80482d0 <printf@plt>
 8048405:   c9                      leave  
 8048406:   c3                      ret    

08048407 <B>:
 8048407:   55                      push   ebp
 8048408:   89 e5                   mov    ebp,esp
 804840a:   83 ec 10                sub    esp,0x10
 804840d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048414:   c9                      leave  
 8048415:   c3                      ret    

我們看到在B ,編譯器保留了0x10字節的堆棧空間,並將我們的int a初始化為[ebp-0x4] int a變量為5。

但是,在A中,編譯器在[ebp-0xc]處放置int a 所以在這種情況下,我們的局部變量並沒有在同一個地方結束! 通過在A添加printf()調用也會導致AB的堆棧幀相同,並打印55

這是未定義的行為 未初始化的局部變量具有不確定的值,使用它將導致未定義的行為。

要記住一件重要的事情 - 不要依賴類似的東西, 永遠不要在真正的代碼中使用它! 這只是一個有趣的事情(甚至並非總是如此),而不是一個特征或類似的東西。 想象一下,你自己試圖找到那種“特征”所產生的錯誤 - 噩夢。

順便說一句。 - C和C ++充滿了那種“功能”,這里有關於它的幻燈片: http//www.slideshare.net/olvemaudal/deep-c因此,如果你想看到更多類似的“功能”,了解什么是在引擎蓋下以及它如何工作只是觀看這個幻燈片 - 你不會后悔,我相信大多數經驗豐富的c / c ++程序員都可以從中學到很多東西。

在函數A ,變量a未初始化,打印其值會導致未定義的行為。

在一些編譯器中,變量aAaB在同一個地址,這樣就可以打印5 ,但同樣,你不能依賴未定義行為。

使用gcc -Wall filename.c編譯代碼。您將看到這些警告。

In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]

In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]  

在c中打印未初始化的變量導致未定義的行為。

第6.7.8節C99標准的初始化說

如果未顯式初始化具有自動存儲持續時間的對象,則其值不確定。 如果未顯式初始化具有靜態存儲持續時間的對象,則:

— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.

EDIT1

如@Jonathon Reinhart如果通過使用-O標志gcc-O0禁用優化,則可能會得到輸出5。

但這並不是一個好主意,永遠不要在生產代碼中使用它。

-Wuninitialized這是一個有價值的警告你應該考慮這一個你不應該禁用或跳過這個警告,導致生產中的巨大損害,如在運行守護進程時導致崩潰。


EDIT2

Deep C幻燈片解釋了為什么結果是5 / garbage.從這些幻燈片中添加這些信息並進行微小修改,以使這個答案更有效。

案例1:沒有優化

$ gcc -O0 file.c && ./a.out  
5

也許這個編譯器有一個它重用的命名變量池。 例如變量,使用和釋放B()則當A()需要一個整數名稱a它將得到變量會得到同樣的存儲器位置。 如果你將B()的變量重命名為b ,那么我認為你不會得到5

案例2:優化

當優化器啟動時可能會發生很多事情。在這種情況下,我猜測可以跳過對B()的調用,因為它沒有任何副作用。 另外,如果A()main()內聯,即沒有函數調用,我也不會感到驚訝。 (但是由於A ()具有鏈接器可見性,因此必須仍然創建該函數的目標代碼,以防另一個目標文件想要與該函數鏈接)。 無論如何,我懷疑如果您優化代碼,打印的值將是其他內容。

gcc -O file.c && ./a.out
1606415608  

垃圾!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM