【问题标题】:Curious segfault with discriminated union and optional<>带有区分联合和可选<>的奇怪段错误
【发布时间】:2016-09-10 15:13:09
【问题描述】:

我想知道在这个使用 std::experimental::optional 和联合类型的简单示例中,什么可能导致段错误。奇怪的是,在 clang 和 gcc 中都会发生段错误,但在两个不同的地方。

我也对从下面的日志中看到的大量复制和破坏感到困惑,想知道是否有更好/惯用的方法来避免这么多明显冗余的操作?在这种情况下,鉴于此处的所有对象都是按值传递和访问的,是否将所有构造函数切换为采用右值引用并在任何地方使用 std::move ?

#include <iostream>
#include <vector>

// https://github.com/akrzemi1/Optional
#include "Optional/optional.hpp"

using std::cout;
using std::vector;
using std::experimental::optional;

struct X {
    int y;

    X(int y) : y(y) { cout << "X::X(int)\n"; }
    X(const X& x) : y(x.y) { cout << "X::X(const X&)\n"; }
    ~X() noexcept { cout << "X::~X()\n"; }
};

struct A {
    vector<X> x;

    A(const vector<X>& x) : x(x) { cout << "A::A(const vector<X>&)\n"; }
    A(const A& a) : x(a.x) { cout << "A::A(const A&)\n"; }
    ~A() noexcept { cout << "A::~A()\n"; }

    static optional<A> get() {
        cout << "A::get()\n";
        return A({ X(1), X(2) });
    }
};

struct M {
    union { A a; };

    M(A a) : a(a) {cout << "M::M(A)\n";}
    M(const M &m) { a = m.a; }

    ~M() noexcept {
        cout << "M::~M()\n";
        (&a)->A::~A();
    }

    static optional<M> get() {
        cout << "M::get()\n";
        auto a = A::get();
        return M(*a);
    }
};

struct P {
    vector<M> m;

    P(const vector<M>& m) : m(m) { cout << "P::P(const vector<M>&)\n"; }
    P(const P& p) : m(p.m) { cout << "P::P(const P&)\n"; }

    static optional<P> get() {
        cout << "P::get()\n";
        auto m1 = M::get();
        auto m2 = M::get();
        vector<M> m;
        cout << "push message 1\n";
        m.push_back(*m1);
        cout << "push message 2\n";
        m.push_back(*m2);
        return P(m);
    }
};

int main() {
    auto p = P::get();
    cout << (*p).m[1].a.x[0].y << "\n";
}

GCC 失败如下:

P::get()
M::get()
A::get()
X::X(int)
X::X(int)
X::X(const X&)
X::X(const X&)
X::X(const X&)
X::X(const X&)
A::A(const vector<X>&)
X::X(const X&)
X::X(const X&)
A::A(const A&)
A::~A()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::X(const X&)
X::X(const X&)
A::A(const A&)
M::M(A)
X::X(const X&)
X::X(const X&)
A::A(const A&)
'./a.out' terminated by signal SIGBUS (Misaligned address error)

#0  0x0000000100003c59 in X* std::__copy_move<false, false, std::random_access_iterator_tag>::__copy_m<X const*, X*>(X const*, X const*, X*) ()
#1  0x000000010000364e in X* std::__copy_move_a<false, X const*, X*>(X const*, X const*, X*) ()
#2  0x0000000100002f3c in __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > > std::__copy_move_a2<false, __gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > > >(__gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > >) ()
#3  0x00000001000025f8 in __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > > std::copy<__gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > > >(__gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X const*, std::vector<X, std::allocator<X> > >, __gnu_cxx::__normal_iterator<X*, std::vector<X, std::allocator<X> > >) ()
#4  0x0000000100001d19 in std::vector<X, std::allocator<X> >::operator=(std::vector<X, std::allocator<X> > const&) ()
#5  0x00000001000012ad in A::operator=(A const&) ()
#6  0x00000001000012d7 in M::M(M const&) ()
#7  0x0000000100001356 in std::experimental::storage_t<M>::storage_t<M>(M&&) ()
#8  0x0000000100001393 in std::experimental::optional_base<M>::optional_base(M&&) ()
#9  0x00000001000013c4 in std::experimental::optional<M>::optional(M&&) ()
#10 0x0000000100001456 in M::get() ()
#11 0x00000001000016a8 in P::get() ()
#12 0x0000000100000db1 in main ()

而 clang 有时不会崩溃,有时会这样:

P::get()
M::get()
A::get()
X::X(int)
X::X(int)
X::X(const X&)
X::X(const X&)
X::X(const X&)
X::X(const X&)
A::A(const vector<X>&)
X::X(const X&)
X::X(const X&)
A::A(const A&)
A::~A()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::X(const X&)
X::X(const X&)
A::A(const A&)
M::M(A)
X::X(const X&)
X::X(const X&)
A::A(const A&)
X::X(const X&)
X::X(const X&)
M::~M()
A::~A()
X::~X()
X::~X()
A::~A()
X::~X()
X::~X()
A::~A()
X::~X()
X::~X()
M::get()
A::get()
X::X(int)
X::X(int)
X::X(const X&)
X::X(const X&)
X::X(const X&)
X::X(const X&)
A::A(const vector<X>&)
X::X(const X&)
X::X(const X&)
A::A(const A&)
A::~A()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::~X()
X::X(const X&)
X::X(const X&)
A::A(const A&)
M::M(A)
X::X(const X&)
X::X(const X&)
A::A(const A&)
X::X(const X&)
X::X(const X&)
M::~M()
A::~A()
X::~X()
X::~X()
A::~A()
X::~X()
X::~X()
A::~A()
X::~X()
X::~X()
push message 1
'./a.out' terminated by signal SIGSEGV (Address boundary error)

【问题讨论】:

  • A一个移动构造函数,使用a(std::move(a))而不是a(a)会删除很多副本
  • 记录 X 的复制和移动构造函数将有助于查看联合是否有问题
  • @M.M 是的,感谢您指出,我已经缩短了所有变量名称,以便于阅读。我已经更新了帖子。
  • @M.M 关于新位置的好点,嗯,但是将 new(&amp;this-&gt;a) A(a) 添加到 M::M(A) 构造函数似乎没有改变任何东西,它仍然在同一个位置出现段错误。
  • 关于副本的数量 - 是的,当底层类型提供 noexcept 移动构造函数时,std::vector 可以更好地运行。这意味着当它需要重新分配其缓冲区时,它可以在之后移动元素,而不是需要复制它们以提供强大的异常安全保证。

标签: c++ c++11 constructor segmentation-fault copy-constructor


【解决方案1】:

您没有在 M const&amp; ctor 案例中构造 A,您只是在它未初始化时对其进行分配。

联合不构造它们的内容。

这与可选无关。

【讨论】:

  • 所以解决方案是在M(const M&amp;)M(A) 中都使用placement new?例如,复制构造函数中的new(&amp;this-&gt;a) A(m.a) 和普通ctor 中的new(&amp;this-&gt;a) A(a)
  • @aldanor:我不是工会专家,但大概是M(const M&amp; m) : a(m.a) {}
  • @LightnessRacesinOrbit 谢谢!不过请参阅下面的评论——因为M 是一个联合类型,不幸的是,这必须在复制构造函数主体中完成
  • @aldanor:我不关注。
  • @ald 是的,一个完整的区分联合需要大量的手动构建(新放置)、销毁、异常安全注意等。查看std::experimantal::variant 的讨论和它的示例实现我的建议。 C++11 允许在联合中使用非 pod 类型,但这并不能使它简单
【解决方案2】:

问题(如果我没记错的话)不是由std::experimental::optional 引起的;它是由struct M 中的union { A a; } 引起的,它在M 复制构造函数中使用。

union { A a; } 是一个 C++11 联合,具有非平凡(具有非平凡构造函数)。所以,如果我没记错的话,构造函数(和析构函数等)被删除了。

我们可以观察到M的拷贝构造函数

M(const M &m) { a = m.a; }

没有初始化列表;所以联合中的a 元素未初始化。什么时候执行任务

a = m.a;

等号左侧的a 未初始化并且(即使:如果我没记错的话)程序的行为是未定义的。

解决方案(我希望):

1) 在A中添加默认构造函数

A() {};

2) 并在复制构造函数的初始化列表中初始化a

M(const M &m) : a() { a = m.a; }

--- 编辑---

更好的解决方案(感谢 Lightness Races in Orbit):使用 A 的复制构造函数在初始化列表中初始化

M(const M &m) : a(m.a) { }

【讨论】:

  • 为什么初始化只是为了重新分配?为什么不M(const M&amp; m) : a(m.a) {};?顺便说一句,你错过了一个冒号。
  • @LightnessRacesinOrbit:天哪!你的权利:更简单的M(const M &amp;m) : a(m.a) {};谢谢
  • 但是由于M 是联合类型,a(m.a) 的初始化风格虽然不会真正起作用。如果有另一个联合字段,比如B b,你必须在复制构造函数体中有一个 switch 语句,你会怎么做?如果AB 没有默认构造函数怎么办?
  • @aldanor - 很明显,您的代码仅适用于工会的单个成员;另一个关键点是M 析构函数:您必须知道联合是否携带AB 才能调用正确的析构函数。我想,为了支持M 复制构造函数,您应该为联合创建一个构造函数。但这是一个完全不同的问题。如果你玩实验......你可以试试std::experimental::any而不是union;或等待std::variant (C++17?)
  • @max66 无法为匿名联合编写构造函数等,逻辑必须在 M 的构造函数等中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多