2014-10-07 8 views
2

假設你用C++編寫了一個函數,但卻心不在焉地忘記輸入字return。在這種情況下會發生什麼?我希望編譯器會抱怨,或者一旦程序到達這一點,至少會出現分段錯誤。但是,實際發生的情況更糟糕:該程序發出垃圾。不僅如此,實際產量取決於優化的水平!下面是一些代碼,說明此問題:在C++中缺少返回值和優化的不規則行爲

#include <iostream> 
#include <vector> 

using namespace std; 

double max_1(double n1, 
     double n2) 
{ 
    if(n1>n2) 
    n1; 
    else 
    n2; 
} 

int max_2(const int n1, 
     const int n2) 
{ 
    if(n1>n2) 
    n1; 
    else 
    n2; 
} 

size_t max_length(const vector<int>& v1, 
      const vector<int>& v2) 
{ 
    if(v1.size()>v2.size()) 
    v1.size(); 
    else 
    v2.size(); 
} 

int main(void) 
{ 
    cout << max_1(3,4) << endl; 
    cout << max_1(4,3) << endl; 

    cout << max_2(3,4) << endl; 
    cout << max_2(4,3) << endl; 

    cout << max_length(vector<int>(3,1),vector<int>(4,1)) << endl; 
    cout << max_length(vector<int>(4,1),vector<int>(3,1)) << endl; 

    return 0; 
} 

這裏就是我得到的,當我在不同的優化級別編譯:

$ rm ./a.out; g++ -O0 ./test.cpp && ./a.out 
nan 
nan 
134525024 
134525024 
4 
4 
$ rm ./a.out; g++ -O1 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 
$ rm ./a.out; g++ -O2 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 
$ rm ./a.out; g++ -O3 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 

現在想象一下你正在試圖調試功能MAX_LENGTH。在生產模式下,你會得到錯誤的答案,所以你在調試模式下重新編譯,現在當你運行它時,一切正常。

我知道有很多方法可以完全避免這種情況下,加入適當的警告標誌(-Wreturn-type),但我仍然有兩個問題

  1. 爲什麼編譯甚至同意編譯功能,而不返回聲明?傳統代碼需要此功能嗎?

  2. 爲什麼輸出取決於優化級別?

+3

1.證明一個函數沒有返回所有路徑並不容易。 2.這是未定義的行爲,所以你不能指望任何特定的輸出。 – juanchopanza 2014-10-07 13:42:46

+1

相關[爲什麼此C++代碼片段編譯(非void函數不返回值)](http://stackoverflow.com/q/20614282/1708801)。基本上,由於它是未定義的行爲,你的結果是不可預知的,編譯器在優化過程中利用UB而臭名昭着。在所有情況下很難察覺到這一點。 – 2014-10-07 13:42:59

+0

你注意警告嗎?將-Werror標誌傳遞給gcc,它不會編譯。 – Slava 2014-10-07 13:48:52

回答

-1

你可能想看看這個答案here

的只是它是編譯器可以讓你沒有return語句,因爲有許多潛在的不同的執行路徑,以確保每一個將退出在編譯時返回可能會非常棘手,因此編譯器會爲您處理它。

請注意以下事項:

如果沒有返回主端將始終返回0

如果另一個函數沒有返回結束它總是在EAX寄存器中返回的最後一個值,通常也是最後一次聲明

優化更改程序集級別上的代碼。這就是爲什麼你會得到奇怪的行爲,編譯器正在修改你的代碼,以便在執行一些事情時給予不同的最後值,從而返回值。

希望這有助於!

+1

如果另一個函數沒有返回就結束了,這是未定義的行爲。 – 2014-10-07 17:53:17

+0

沒錯,因爲你不能保證執行的最後一條語句是eax寄存器中的內容,但它是返回時使用的寄存器,因此就是調用者函數抓取的內容。除非它是一個非整數類型,但是它是XMM或FPU堆棧或任何類型。所以是沒有定義的,但通常是最後一個聲明的結果。 – 2014-10-07 18:22:51

+1

不,這只是未定義的。這與eax寄存器無關。它可能是這種情況,它通常是您的機器上的eax寄存器和您的編譯器的最後一個值,但我可以在每次發生這種情況時使編譯器返回47,並且它將100%符合標準。 – 2014-10-07 18:59:35

9

這是undefined behavior脫落的值返回函數結束,這是包括在draft C++ standard部分`31年6月6日return語句它說:

流下的函數結束相當於沒有 價值的回報;這會導致在返回值 函數中出現未定義的行爲。

編譯器不需要發出診斷,我們可以從部分1.4實現達標它說看到這一點:

這套診斷的規則,包括所有的句法和語義 規則本國際標準除含有 的那些規則外,明確標註「不需要診斷」或將 描述爲導致「未定義行爲」。

雖然編譯器通常會嘗試捕獲大量未定義的行爲併產生警告,但通常您需要使用正確的標誌集。對於gccclang我發現下面的一組標誌是有用的:

-Wall -Wextra -Wconversion -pedantic

和一般我會鼓勵你把警告到使用-Werror錯誤。

編譯器是臭名昭著在優化階段趁着未定義行爲,請參閱Finding Undefined Behavior Bugs by Finding Dead Code一些好的例子包括臭名昭著的Linux內核空指針檢查除塵凡在處理這個代碼:

struct foo *s = ...; 
int x = s->f; 
if (!s) return ERROR; 

gcc推斷,因爲ss->f;中被引用,並且由於取消引用空指針是未定義的行爲,因此s不能爲空,因此優化了下一行的if (!s)檢查(複製自我的answer here)。

由於未定義的行爲是不可預知的,那麼在更積極的設置下,編譯器在許多情況下會做更積極的優化,其中許多可能沒有多少直觀的意義,但是,嘿它是未定義的行爲,所以你應該沒有任何期望。

請注意,雖然有很多情況下編譯器可以確定一個函數沒有正確返回一般情況下,這是halting problem。在運行時自動執行此操作會帶來違反不支付您不使用哲學的成本。儘管gccclang都實現了sanitizers來檢查未定義行爲,例如使用-fsanitize=undefined標誌將在運行時檢查未定義的行爲。