2017-06-29 139 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)。再次感谢。 –