2011-07-21 38 views
4

我剛剛在代碼中發現了一個令人討厭的bug,因爲我通過引用獲取了一個字符串的const引用。到lambda運行時,原始字符串對象已經很久了,引用的值是空的,而目的是它將包含原始字符串的值,因此是錯誤。lambda:應該通過引用捕獲const引用yield undefined behavior?

什麼令我感到困惑的是,這並沒有調用運行時崩潰:畢竟,不應該因爲據我所知有一個懸空參考這個是不確定的行爲?而且,當在調試器下查看id時,它甚至不會看起來像垃圾,而只是一個正確構造的空字符串。

這裏的測試案例;這只是打印一個空行:

typedef std::vector< std::function< void() > > functions; 

void AddFunction(const std::string& id, functions& funs) 
{ 
    funs.push_back([&id]() 
    { 
     //the type of id is const std::string&, but there 
     //is no object to reference. UB? 
     std::cout << id << std::endl; 
    }); 
} 

int main() 
{ 
    functions funs; 
    AddFunction("id", funs); 
    funs[ 0 ](); 
} 
+0

你又爲const引用結合:( –

+0

是的,我已經知道了,幸運的是,單元測試指出了這一點 – stijn

+0

你可能已經不太幸運臨時的受害者,它可能已經工作沒有問題的。試想一下,編譯器調整AddFunction'調用之後'棧,但是在臨時居住的堆棧區仍然完好無損。後來有一天,KABOOM! –

回答

4

未定義的行爲意味着沒有要求會發生什麼。沒有要求它會崩潰。無論你的記憶晃來晃去的參考點,沒有理由它應該包含的東西,看起來像一個空字符串,這是合理的,的string析構樹葉在該州的內存。

+0

所以這只是一個巧合? – stijn

+0

@stijn:它不能保證,但如果析構函數確實離開內存看起來像一個空的字符串,這可能不是偶然的,這將是因爲它清除了一些字段。僥倖的部分是用於包含臨時字符串的堆棧區域尚未被重用。 –

+0

只是爲了測試我添加上面的代碼在實際應用中,有我得到運行時死機確實,你的狀態的確切原因:曾經的記憶得到重用串去所有虛假 – stijn

3

通過引用捕獲任何東西意味着必須小心它的活着足夠長。如果你不知道這個程序可能正常工作,但它可能只是打電話給多米諾骨牌,然後點一個雙重意大利辣香腸。至少,根據標準。

+2

唉,還沒有人出了我一個編譯器那實際上會*在UB上訂購披薩,所以我開始對這個例子保持警惕。 –

+0

我寧願發送補丁與一些IP地理定位:-) – dascandy

+0

@Kerrek SB海合會人:你需要在代碼中引入更多的瑞銀,以提高勝算:) –

2

(正如dascandy所指出的那樣)這個問題與常量和引用語法幾乎沒有關係,更簡單的是它是一種責任的放棄,以確保在任何時候通過引用傳遞的所有內容的存在引用。函數調用中的字面量對於該調用來說是嚴格臨時的,並且在返回時會蒸發,所以我們正在訪問一個臨時的 - 編譯器經常檢測到的缺陷 - 而不是在這種情況下。

typedef std::vector<std::function<void()> > functions; 

void AddFunction(const std::string& id, functions& funs) { 
    funs.push_back([&id]() 
    { 
     //the type of id is const std::string&, but there 
     //is no object to reference. UB? 
      std::cout <<"id="<< id << std::endl; 
     }); 
} 

int emain() { 
    functions funs; 

    std::string ida("idA"); 
      // let idB be done by the tenporary literal below 
    std::string idc("idC"); 

    AddFunction(ida, funs); 
    AddFunction("idB", funs); 
    AddFunction(idc, funs); 
    funs[0](); 
    //funs[1](); // uncomment this for (possibly) bizarre results 
    funs[2](); 
    std::cout<<"emain exit"<<std::endl; 
    return 0; 
} 

int main(int argc, char* argv[]){ 
    int iret = emain(); 
    return 0; 
}