2017-06-29 138 views
17

下面的問題是從一個巨大的項目中提煉出來的,也是我能夠想到的問題的最小例子。尋找堆棧損壞錯誤的解釋

我知道,從std::string得到的結果很糟糕,它已經在我們的代碼庫中發生了變化,但我試圖理解這裏隱藏的內容。

的代碼在釋放模式崩潰上的Visual C++ 2017

Microsoft Visual Studio Community 2017 
Version 15.2 (26430.14) Release 
Visual C++ 2017 00369-60000-00001-AA257 

只(以速度優化)。沒有速度優化,它不會在發佈模式下崩潰。

#include <string> 
#include <string_view> 
#include <vector> 

struct my_string : public std::string 
{ 
    __declspec(noinline) 
     my_string::my_string(const std::string_view& str) : 
     std::string(str.data(), str.size()) 
    {} 

    template <typename T> 
    my_string& arg(T) 
    { 
     return *this; 
    } 
}; 

struct my_string_view : public std::string_view 
{ 
    my_string_view(const std::string_view::value_type* val) : 
     std::string_view(val) {} 

    template <typename... PARAMS> 
    my_string arg(PARAMS&&... prms) { 
     return my_string(*this).arg(std::forward<PARAMS>(prms)...); 
    } 
}; 

template <typename T> 
struct basic_color 
{ 
    T r, g, b, a; 

    basic_color() : r(0), g(0), b(0), a(255) {} 

    template <typename U> 
    explicit basic_color(const basic_color<U>& c) : 
     r(c.r), g(c.g), b(c.b), a(c.a) 
    {} 
}; 

using color = basic_color<std::uint8_t>; 
using float_color = basic_color<float>; 

__declspec(noinline) 
void change_float_color(float_color& color) 
{ 
    color.r = 0.1f; 
} 

int main() 
{ 
    std::vector<float_color> colors = { {} }; 
    float sum = 0; 
    for (std::uint32_t i = 0; i < 1; ++i) 
    { 
     float_color fc; 
     change_float_color(fc); 
     color c(fc); 
     std::vector<std::string> msgs; 
     msgs.push_back(my_string_view("").arg(c.r)); 
     msgs.push_back(my_string_view("").arg(c.g)); 
     sum += fc.b - colors[i].b; 
    } 
    return static_cast<int>(sqrt(sum)); 
} 

在Visual Studio中的錯誤是這樣的(看看的msgscolors破碎的尺寸在底部):

enter image description here

我的猜測是,std::vector<std::string>::push_back(std::string&&)與通話my_string有問題(切片行爲)。但是如何破壞堆棧(或堆棧指針)呢?

有沒有人有一個想法可能發生在這裏或我如何能找出?

Here是我的項目,以防萬一任何人有興趣重現問題。

+4

使用調試器,數據斷點是這裏選擇的武器。 –

+0

「因爲std :: string沒有虛擬析構函數,所以給push_back賦予的r值被切片了。」切片與虛擬析構函數無關。無論如何,這些值都會被切片。 「因此,派生類(my_string)的析構函數永遠不會被調用,一些垃圾仍然在堆棧中」這沒有任何意義。 'sizeof(my_string)== sizeof(std :: string)'這不會使一點區別。 「看看破損的大小」編譯器可以隨意對這些對象做任何事情,因爲它們將永遠不會再被使用。 –

+0

>我的第一個猜測是,由於std :: string沒有虛擬析構函數,因此賦予push_back的r值被切片。因此,無論析構函數是否被調用,派生類(my_string)的析構函數都不會被調用<爲這個類分配的內存(在這種情況下爲堆棧)。而且,'〜my_string' **被**調用,因爲當'my_string'被複制到'std :: string'中時發生切片,並且原始字符串不是,也不能被切片。 – Hedede

回答

4

我認爲這是一個編譯器錯誤。

下面是我從拆卸中看到的:在main()條目中,esp保存爲ebx。最後,espebx恢復。但是,在中間(呼叫std::_Destroy_range1後)ebx值被其他東西覆蓋。因此,最後,ret指令使用僞造的值esp,並跳轉到無效位置。

所以,實際上,堆棧沒有損壞(這個錯誤不能像漢斯所說的那樣用數據斷點捕捉),但堆棧指針是。

+0

感謝您重現問題。我希望[this](https://social.msdn.microsoft.com/Forums/vstudio/en-US/b40ebfa9-9fdb-44f9-a778-6c7e24e1c2e4/stack-corrupting-due-to-a-bug-in- the-compileroptimizer?forum = vcgeneral)是MSDN上發佈該問題的正確位置。無論如何,我會檢查問題是否隨着下一次編譯器更新的正式發佈而消失。 –

+0

@Tobias:[vcblog](https://blogs.msdn.microsoft.com/vcblog/)建議使用[Connect](https://connect.microsoft.com/VisualStudio)網站報告錯誤。我同意你的評論:這個bug是脆弱的,我們只能確定它是固定的,如果一個VS開發者說「是的,我已經分析了這個問題並修復了它」 – geza

+0

OK,[done](https:// connect .microsoft.com/VisualStudio中/反饋/詳細信息/ 3136753)。再次感謝。 –