【问题标题】:Why do Clang and VS2013 accept moving brace-initialized default arguments, but not GCC 4.8 or 4.9?为什么 Clang 和 VS2013 接受移动大括号初始化的默认参数,但不接受 GCC 4.8 或 4.9?
【发布时间】:2014-01-07 21:36:57
【问题描述】:

如标题所示,我有一个简短的演示程序,可以使用所有这些编译器进行编译,但在使用 gcc 4.8 和 gcc 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 ***

【问题讨论】:

  • 你在 gcc 上使用了哪些标志?
  • 稍微简化的版本:coliru.stacked-crooked.com/a/7be23631a99ee9f0(简化为默认参数{}std::unordered_map构造函数/析构函数)
  • @DanielFrey,参考?我不认为继承标准容器是非法的。
  • @vmrob 我记得看过一个错误报告,我认为Foo f = {} 是罪魁祸首。 Here it is。不过,这可能无关紧要。如果是这样,请忽略我的评论。
  • I'm not sure why I first thought it was with libstdc++, but after realizing that just adding that explicitly defined default destructor fixed it, I couldn't imagine it being the library. 我不得不同意,我认为这与 unordered_map 无关。请参阅 thisthis。不过我可能错了。

标签: c++ gcc c++11 list-initialization


【解决方案1】:

更新

它显示为fix for the problem has been checked in


有趣的问题。 GCC 如何处理 = {} 初始化的默认参数,这绝对是一个错误,这是一个late addition to the standard。这个问题可以用一个非常简单的类代替std::unordered_map&lt;int,int&gt;来重现:

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

Output:

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 4.9 中存在的错误(或者至少是我拥有的快照。)clang 没有相同的错误。已经有一个错误报告和其他一些看起来相似的错误报告。但是 +1 用于澄清测试用例的问题。如果你这样做SimpleClass x = SimpleClass{},问题就会消失。
  • 是的,抱歉,我现在看到了关于这个问题的完整评论链。我之前没有通读所有隐藏的 cmets。哦,好吧,无论如何,我发现这是一个有趣的练习:)
  • @remyabel 我尝试了几个不同的默认参数,只有= {...} 触发了这个错误。我不得不猜测它与后期添加有关,但它仍然很奇怪,因为它应该与SimpleClass x = SimpleClass{}完全相同,只是语法变化。 &amp;x == 0x7fffdde483bf 时的输出来自使用该语法的代码。
  • @jerry 我认为= SimpleClass{}= {} 的语义不同。第一个是非 POD 类型的默认初始化,与 POD 类型的 SimpleClass a; memset(a,0,sizeof(a)); 几乎相同,而 Simpleclass a = {} 是通过非 POD 类型的 std::initializer_list 构造和 POD 类型的聚合初始化。不过,我从来没有真正使用过= SimpleClass{} 表单,所以我可能错了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-07
  • 2015-05-04
  • 1970-01-01
  • 2015-07-22
  • 2018-07-02
  • 2011-06-13
相关资源
最近更新 更多