标记联合中字符串影响的分段错误

时间:2018-11-11 23:05:26

标签: c++ unions stdstring

我有以下简化的代码,它们对可以为整数或字符串的变量建模:

//tagged union
union VariableValueType {
    std::int64_t  i;
    std::string   s;

    VariableValueType(void): i(0) {}
    ~VariableValueType(void) {}
};
enum class VariableType {Undefined, Integer, String};

class Variable
{
    VariableValueType _value;
    VariableType _type;

    //constructor from a string
    Variable(const VariableType type, const std::string& value)
     : _type(VariableType::Undefined)
    {
        set(type,value);
    }

    // setter
    void set(const VariableType type, const std::string& value)
    {
        switch(type)
        {
            case VariableType::Integer :
                _value.i = std::stoi(value);
                _type = VariableType::Integer;
                break;
            case VariableType::String :
                _value.s = value; //<-- crashes here!!!
                _type = VariableType::String;
                break;
        }
    }
};

当我使用set从代码的另一点调用type=String时,在字符串影响行上出现了分段错误。

#0  0x00007ffff7b70c73 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_assign(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00007ffff7b70f89 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x00005555556c3fdd in Variable::set (this=0x5555559d7ae0, type=VariableType::String, value="1") at /home/.../Variable.cpp:118

我不知道这里发生了什么...

有趣的事实:如果我将std::string foo = value;添加为set方法的第一行,那么分段错误就会消失...


修改

当我问gdb _value.s的值时,我收到以下消息:

error reading variable: Cannot create a lazy string with address 0x0, and a non-zero length.

我不明白...

2 个答案:

答案 0 :(得分:2)

不要在联合中使用字符串。请勿在联合中对非POD的对象(对于构造函数/析构函数)不使用任何东西。

原因是不会调用您的构造函数,这意味着该对象将具有未定义的状态。

请改用std::variant(如果没有C ++ 17,请使用boost::variant)。

答案 1 :(得分:2)

正如评论和其他答案中所解释的那样,问题出在以下事实:不是POD的并集元素不能简单地初始化:应该使用新的placement。使用此类联合时,还必须注意在不再使用非POD元素时将其删除。

在这里,它会像这样:

// before assigning something else than a string
if(_type == VariableType::String) _value.s.~std::string();

// to assign a string if current type is *not* a string
new(&_value.s) std::string(value);
// to assign a string if current type is a string
_value.s = value;

还曾提到,使用专用库可以更轻松地处理具有非POD元素的此类联合:

  • 在C ++ 17中直接以std::variant的身份提供
  • 通过Boost以boost::variant的形式提供
  • 可通过mapbox / variant获得(Boost替代品:更轻的库)