Why are Clang and VS2013 accepting initial arguments by default, but not GCC 4.8 or 4.9?

As in the title, I have a small demo program that compiles with all of these compilers, but with core dumps on startup after compilation with gcc 4.8 and gcc 4.9:

Any ideas as to why?

#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); } 

My compilation settings:

 g++ --std=c++11 main.cpp 

Here is the backtrace from 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 ***

+48
c ++ gcc c ++ 11
Jan 07
source share
1 answer

Update

It looks like the fix for the problem has been checked in .




Interest Ask. This seems to be a bug in the way GCC handles default initialized arguments = {} , which was a late addition to the standard . The problem can be reproduced using a fairly simple class instead of 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 , it displays the same problem:

 *** 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 

Digging a little deeper, GCC seems to create an additional object (although it only calls the constructor and destructor once) when you use this syntax:

 #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; } 

Output :

 In constructor: 0x7fffbf873ebf In DefArgFunc: 0x7fffbf873ea0 In destructor: 0x7fffbf873ebf 

Changing the default argument from SimpleClass x = {} to SimpleClass x = SimpleClass{} causes

 In constructor: 0x7fffdde483bf In DefArgFunc: 0x7fffdde483bf In destructor: 0x7fffdde483bf 

as was expected.

It seems that the object is being created, the default constructor is called, and then something similar to memcpy is executed. This "ghost object" is what is passed to the move constructor and modified. However, the destructor is called on the original, unmodified object, which now shares some pointer with the object created by the motion. Both eventually try to free him, causing a problem.

The four changes you noticed corrected the problem, given the above explanation:

 // 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); 

Passing an argument to the constructor ( Bar b(Foo{}); ) instead of using the default argument also solves the problem.

+11
Jan 15 '14 at 19:29
source share



All Articles