花了很多天时间在一个复杂的程序中调试了一个非常模糊的错误之后,我终于将问题简化为一个非常简单,可重现的方案。
基本上,看来,如果我有一个简单的非捕获lambda,其签名按值接受单个参数(并按值返回一个对象),然后将该lambda转换为等效的函数指针类型,然后调用函数指针,作为参数传递的对象的析构函数被错误地调用。
这是一个简单的可重现的案例,显示了此问题,并使用GCC 4.9.2导致了双重释放:
#include <iostream>
#include <memory>
#include <utility>
#include <cassert>
struct Foo
{
Foo() = default;
Foo(const Foo&) = delete;
Foo& operator = (const Foo&) = delete;
~Foo()
{
std::cout << "Destroying object " << this << std::endl;
}
};
auto callback = [](std::unique_ptr<Foo> p)
{
assert(p);
return p;
};
int main()
{
std::unique_ptr<Foo> ptr(new Foo());
auto fptr = static_cast<std::unique_ptr<Foo>(*)(std::unique_ptr<Foo>)>(callback);
auto result = fptr(std::move(ptr));
}
该程序的预期行为是分配由Foo
管理的unique_ptr
的唯一实例。然后通过将该实例作为参数传递给callback
来移动该实例一次,然后在callback
按值返回它时再次移动该实例,最后破坏该实例。 (当然,编译器还可以使用copy-elision删除至少一个move构造函数调用,但这并不重要。)
因此,我实际上应该只看到Foo
的析构函数被调用一次。
相反,当我运行此程序时,我会看到以下内容:
Destroying object 0xdf4410
Destroying object 0xdf4410
*** Error in `./test9': double free or corruption (fasttop): 0x0000000000df4410 ***
不知何故,编译器生成的代码在传递给unique_ptr
时会错误地调用callback
析构函数。
请注意,如果我将非捕获的lambda转换为等效的函数指针,则会发生这种 only 的情况。如果我直接调用callback
,就没有问题。
所以我不知道为什么会发生这种情况,因此我想问这是否可能是带有lambda到函数指针转换的编译器错误,还是我在这里做错了什么。