2014-12-02 66 views
0

這很奇怪。C++ 11類型轉換heisenbug(XCode 6.1,clang)

OSX 10.10 LLVM 6.0 的XCode 6.1

test_assert("Wierd", String{"ABC"}, "ABC"); // claims not equal 

字符串是我的自定義類(包裝一個Python字符串原語),並應通過這個測試。

這裏的test_assert,添加了調試輸出:

template <typename B, typename V> 
static void test_assert(std::string description, B benchmark, V value) 
{ 
    std::ostringstream full_description; 

    full_description << description 
     << " : { " << "benchmark"  << ", " << "value"   << " }" 
     << " = { " << typeid(B).name() << ", " << typeid(V).name() << " }" 
     << " , { " << benchmark  << ", " << value   << " }"; 

    // N2Py6StringE, PKc i.e. Py::String and const char* (Pointer to Konst Char) 
    std::cout << typeid(B).name() << ", " << typeid(V).name() << std::endl; 

    V b_as_v{static_cast<V>(benchmark)}; 

    // wtf? b_as_v: \352\277_\377 -- should be "ABC" 
    std::cout << "b_as_v: " << b_as_v << std::endl; // Y 

    if(b_as_v == value) 
     std::cout << " PASSED: " << full_description.str() << std::endl; 
    else 
     throw TestError(full_description.str()); 
} 

正是這種b_as_v{static_cast<V>(benchmark)};被扔我,因爲如果我單步執行到它,它正確地把我帶到字符串的「轉換爲const char *」操作,執行其職責正確:

class String : Object { 
    explicit operator const char*()             const 
    { 
     std::string s{ as_std_string() }; 
     const char* c{ s.c_str() }; 

     // c before return: ABC 
     std::cout << "c before return: " << c << std::endl; // X 

     return c; 
    } 
    : 

現在,這是奇怪的事情:如果行X到位,線Y報道什麼: 'b_as_v:'

行刪除它,Y行報告原始:'b_as_v:\ 352 \ 277_ \ 377'

事實上,只需打印std::cout << std::endl; // X' X足以清除Y的輸出(但是,將X'移動到緊挨着Y恢復原來的行爲)。

所以看來觀察行爲會改變返回值。

heisenbug>:|

而這兩種行爲都不是理想的行爲。

另一個奇怪之處在於,如果我將粘貼從Xcode控制檯複製到SO文本編輯窗口,那麼在'\ 352 \ 277_ \ 377'的末尾會有一個額外的Unicode字符複製到剪貼板。

enter image description here

即使我只選擇最後7它仍然副本劃過,即使它不佔用在Xcode的控制檯中的空白。

(這個額外的字符不會顯示在SO問題上,實際上當我重新打開編輯問題時,它不再存在,它不是 換行符 - 我測試過的複製粘貼-ing分詞在特定行的最後一個字符)

我試圖創建一個測試用例,但可悲的是,我所期望的執行:http://ideone.com/gbyU6Y

回答

3

一個相當複雜的設置,但病因是相當簡單:

explicit operator const char*() const 
{ 
    std::string s{ as_std_string() }; 
    const char* c{ s.c_str() }; 

    // c before return: ABC 
    std::cout << "c before return: " << c << std::endl; // X 

    return c; 
} 

std::string::c_str()返回的指針指向std::string的內部存儲器,因此可能因多種原因而失效 - 銷燬std::string對象就是其中之一。這裏,c只要您的轉換函數返回並且s被銷燬就會失效,這意味着返回一個懸掛指針。另外,libC++使用小字符串優化,這意味着與"ABC"一樣短的字符串存儲在std::string對象本身(在本例中,在堆棧中)內,而不是存儲在動態分配的存儲器中。這使得在您的代碼嘗試打印之前,曾經被字符串佔用的空間可能會被重用。

+0

感謝T.C.,這是否意味着我可以安全地返回一個const char *的唯一方法是通過malloc - 它,並要求消費者隨後釋放它? – 2014-12-02 11:46:49

+1

@Pi或者在你的'String'類中存儲'std :: string'。或者只是返回一個'std :: string',讓消費者調用'.c_str()'。 – 2014-12-02 11:48:26