为什么Clang和VS2013接受移动大括号初始化的默认参数,但不接受GCC 4.8或4.9?
就像标题所暗示的,我有一个简短的演示程序,可以编译所有这些编译器,但在使用gcc 4.8和gcc 4.9编译后运行核心转储:为什么Clang和VS2013接受移动大括号初始化的默认参数,但不接受GCC 4.8或4.9?
有关为什么?
#include <unordered_map>
struct Foo : std::unordered_map<int,int> {
using std::unordered_map<int, int>::unordered_map;
// ~Foo() = default; // adding this allows it to work
};
struct Bar {
Bar(Foo f = {}) : _f(std::move(f)) {}
// using any of the following constructors fixes the problem:
// Bar(Foo f = Foo()) : _f(std::move(f)) {}
// Bar(Foo f = {}) : _f(f) {}
Foo _f;
};
int main() {
Bar b;
// the following code works as expected
// Foo f1 = {};
// Foo f2 = std::move(f1);
}
我的编译设置:
g++ --std=c++11 main.cpp
下面是GDB回溯:
#0 0x00007fff95d50866 in __pthread_kill()
#1 0x00007fff90ba435c in pthread_kill()
#2 0x00007fff8e7d1bba in abort()
#3 0x00007fff9682e093 in free()
#4 0x0000000100002108 in __gnu_cxx::new_allocator<std::__detail::_Hash_node_base*>::deallocate()
#5 0x0000000100001e7d in std::allocator_traits<std::allocator<std::__detail::_Hash_node_base*> >::deallocate()
#6 0x0000000100001adc in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<int const, int>, false> > >::_M_deallocate_buckets()
#7 0x000000010000182e in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_deallocate_buckets()
#8 0x000000010000155a in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::~_Hashtable()
#9 0x000000010000135c in std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, int> > >::~unordered_map()
#10 0x00000001000013de in Foo::~Foo()
#11 0x0000000100001482 in Bar::~Bar()
#12 0x0000000100001294 in main()
*** error for object 0x1003038a0: pointer being freed was not allocated ***
更新
它会出现一个fix for the problem has been checked in。
有趣的问题。它肯定似乎是GCC处理= {}
初始化默认参数的一个错误,这是一个late addition to the standard。这个问题可以用一个非常简单的类的地方被复制的std::unordered_map<int,int>
:
#include <utility>
struct PtrClass
{
int *p = nullptr;
PtrClass()
{
p = new int;
}
PtrClass(PtrClass&& rhs) : p(rhs.p)
{
rhs.p = nullptr;
}
~PtrClass()
{
delete p;
}
};
void DefArgFunc(PtrClass x = {})
{
PtrClass x2{std::move(x)};
}
int main()
{
DefArgFunc();
return 0;
}
Compiled with g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1,它显示了同样的问题:
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000001aa9010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fc2cd196b96]
./a.out[0x400721]
./a.out[0x4006ac]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fc2cd13976d]
./a.out[0x400559]
======= Memory map: ========
bash: line 7: 2916 Aborted (core dumped) ./a.out
挖得更深一些,GCC似乎产生了一个额外的对象(虽然它只是调用构造函数和析构函数各一次),当您使用此语法:
#include <utility>
#include <iostream>
struct SimpleClass
{
SimpleClass()
{
std::cout << "In constructor: " << this << std::endl;
}
~SimpleClass()
{
std::cout << "In destructor: " << this << std::endl;
}
};
void DefArgFunc(SimpleClass x = {})
{
std::cout << "In DefArgFunc: " << &x << std::endl;
}
int main()
{
DefArgFunc();
return 0;
}
In constructor: 0x7fffbf873ebf
In DefArgFunc: 0x7fffbf873ea0
In destructor: 0x7fffbf873ebf
改变从SimpleClass x = {}
默认参数SimpleClass x = SimpleClass{}
产生
In constructor: 0x7fffdde483bf
In DefArgFunc: 0x7fffdde483bf
In destructor: 0x7fffdde483bf
如预期。
似乎在发生的事情是创建对象,调用默认构造函数,然后执行与memcpy
类似的操作。这个“鬼物体”是传递给移动构造函数并进行修改的东西。但是,析构函数是在原始的,未修改的对象上调用的,该对象现在与移动构造的对象共享一些指针。两者最终都试图释放它,造成这个问题。
,你注意到的四个转变固定给出的上述解释的问题是有意义的:
// 1
// adding the destructor inhibits the compiler generated move constructor for Foo,
// so the copy constructor is called instead and the moved-to object gets a new
// pointer that it doesn't share with the "ghost object", hence no double-free
~Foo() = default;
// 2
// No `= {}` default argument, GCC bug isn't triggered, no "ghost object"
Bar(Foo f = Foo()) : _f(std::move(f)) {}
// 3
// The copy constructor is called instead of the move constructor
Bar(Foo f = {}) : _f(f) {}
// 4
// No `= {}` default argument, GCC bug isn't triggered, no "ghost object"
Foo f1 = {};
Foo f2 = std::move(f1);
将参数传递给构造(Bar b(Foo{});
),而不是使用默认的说法也解决了这个问题。
你在gcc上使用了哪些标志? – ThomasMcLeod
为什么在使用这个构造函数时会发生:'Bar(Foo f = {Foo()}):_f(std :: move(f)){} – ThomasMcLeod
它的核心转储,原因相同。 – vmrob