2015-12-21 146 views
24

假設下面的代碼:是否有可能從引用中產生分段錯誤?

Foo & foo = getFoo(); 
foo.attr; // 100% safe access? 

如果foo是一個指針,我會檢查它是否是NULL,但是因爲它是一個參考,這樣的檢查是不必要的。我想知道的是,如果可能搞砸一個對象的引用,使得訪問它的屬性不安全。

我嘗試了一些例子,比如試圖將NULL轉換爲Foo對象,但是出現了編譯錯誤。我只是想確定上面的代碼總是安全的,並且我不應該知道內在的黑魔法。

從Benjamin的回答,我可以做一個示例代碼,我從中得到segmentation fault引用,因此它回答了我的問題。我會粘貼我的代碼,以防有人對未來感興趣:

#include <iostream> 
using namespace std; 

class B 
{ 
    public: 
    int x; 
    B() {x = 5;} 
}; 
class A 
{ 
    public: 
    void f() 
    { 
     b = *(B*)NULL; 
    } 
    B & getB() 
    { 
     return b; 
    } 

    B b; 
}; 

int main() 
{ 
    A a; 
    a.f(); 

    cout << a.getB().x << endl; 
    return 0; 
} 
+0

顯示的'getFoo'功能,以便更好地回答你的問題。 –

+0

在我真正的問題中,'getFoo'有數百行代碼,它返回的對象可能會被數百個線程修改...因此,不可能以'getFoo'爲例。 – Kira

+1

@Kira:請注意,您的段錯誤幾乎肯定來自'f()'函數中的語句'b = *(B *)NULL;'。不是來自'main'中的'a.getB()。x'。 –

回答

28

是的,這是可能的。

Foo& Fr = *(Foo*)nullptr; 

從技術上講,這已經是取消引用該指針的未定義行爲。但它很可能不會導致任何可觀察的錯誤。這大概雖然:

Fr.attr = 10; 

然而,正如喬納森Wakely在評論中指出,沒有任何理由讓你檢查這樣的情況。如果一個函數返回一個無效的引用,那個函數就會中斷,需要修復。假設參考是有效的,您的使用代碼不會被破壞。但是,正如David Schwartz的回答中所述,有效的引用將變爲在完全合法的代碼中無效(儘管不爲空)。但是你沒有辦法檢查這個。你只需要知道它會在什麼情況下發生,然後停止使用該參考。

+1

雖然這是真的,但這種情況只能出現在其他地方有bug的代碼中。當然,訪問被引用對象是唯一錯誤的情況至少同樣重要,至少值得一提。 –

+1

@DavidSchwartz:當然。我贊成你的答案。 –

16

有可能引用壞記憶。所以foo.attr; // 100% safe access?的答案是否定的。考慮下面的例子:

int &something() { 
    int i = 5, &j = i; 
    return j; // Return reference to local variable. This is destroyed at end of scope. 
} 

int main() { 
    int &k = something(); // Equivalent to getFoo() 
    std::cout << k << endl; // Using this reference is undefined behavior. 
    return 0; 
} 

Live example.

參考k被沒有指向合法存儲器。但是這仍然會編譯。然而,在程序員沒有犯錯的情況下,這種情況不會發生。在這種情況下,功能something()寫入不正確,需要修復。沒有辦法或理由來檢查這一點。如果一個函數返回一個錯誤的引用,那麼你可以(也應該)做的唯一的事情就是修復違規函數。

+0

我對Bejamin的這個問題有同樣的反對意見。雖然是真的,但這是最不重要的情況,因爲只有在其他地方存在代碼錯誤時纔會發生。 –

+0

@DavidSchwartz但我已經提到過這個?我不確定是否清楚,但在回答這個問題時:'foo.attr; // 100%安全訪問?'我相信我已經正確回答。不,如果'getFoo()'的程序員犯了一個錯誤。 –

+0

我對你提到的沒有任何問題。我的問題是你沒有提到 - 干預代碼可以使一個有效的參考不再有效。 –

23

引用必須引用該引用就位時的有效對象。這是一個C++標準要求,違反它的任何代碼都是UB,並且可以做任何事情。

但是,在引用被引用之後銷燬引用引用的對象是完全合法的。在那一點上,訪問參考是非法的。例如:

std::vector<int> j; 
j.push_back(3); 
int& k = j.front(); // legal, object exists now 
j.clear();   // legal, object may be destroyed while reference exists 
k++;    // illegal, destruction of object invalidates reference 

這意味着返回引用的函數必須始終返回返回時有效的引用。這就是爲什麼在空載體上調用front就是UB的原因 - 一個參考在坐下時必須有效。但是,通常會有條件導致該引用失效,並且如果您計劃嘗試存儲引用並稍後訪問它,則需要了解這些條件是什麼。

通常,您應該假設存儲返回的引用並稍後訪問它是不安全的,除非您知道引用將保持有效。例如,std::vector小心地解釋了在什麼情況下對容器的引用可以失效,並且包括對push_back的後續調用。因此,這被打破:

std::vector<int> j; 
j.push_back(3); 
int &first = j.front(); 
j.push_back(4); 
int &second = j.back(); 
if (first == second) // illegal, references into container are invalidated by push_back 

但是,這是罰款:

std::vector<int> j; 
j.push_back(3); 
j.push_back(4); 
int &first = j.front(); 
int &second = j.back(); 
if (first == second) // legal, references into container stay valid 
+4

關於OP的注意事項,在執行'k ++'之前,沒有辦法檢測'k'是無效的。 ''k'的有效性是你必須通過你的程序的邏輯確保達到那個點。 –

相關問題