来自非引用std :: vector参数的函数调用期间的分段错误

时间:2016-07-01 11:58:42

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

在我的程序中的某个时刻,我在下面g()的行中遇到了分段错误:

// a data member type in MyType_t below
enum class DataType {
    BYTE, SHORT, INT, VCHAR, FLOAT, DOUBLE, BOOL, UNKNOWN
};

// a data member type in MyType_t below
enum class TriStateBool {
    crNULL, crTRUE, crFALSE
};

// this is the underlying type for std::vector used when 
// calling function f() from g(), which causes the segmentation fault
struct MyType_t {
    DataType    Type;
    bool        isNull;
    std::string sVal;
    union {
        TriStateBool  bVal;
        unsigned int  uVal;  
        int           nVal;
        double        dVal;
    };

    // ctors
    explicit MyType_t(DataType type = DataType::UNKNOWN) :
        Type{type}, isNull{true}, dVal{0}
    { }

    explicit MyType_t(std::string sVal_) : 
        Type{DataType::VCHAR}, isNull{sVal.empty()}, sVal{sVal_}, dVal{0}
    { }

    explicit MyType_t(const char* psz) : 
        Type{DataType::VCHAR}, isNull{psz ? true : false}, sVal{psz}, dVal{0}
    { }

    explicit MyType_t(TriStateBool bVal_) : 
        Type{DataType::BOOL}, isNull{false}, bVal{bVal_} 
    { }

    explicit MyType_t(bool bVal_) : 
        Type{DataType::BOOL}, isNull{false}, bVal{bVal_ ? TriStateBool::crTRUE : TriStateBool::crFALSE} 
    { }

    MyType_t(double dVal_, DataType type, bool bAllowTruncate = false) {
        // sets data members in a switch-block, no function calls
        //...
    }
};

void f(std::vector<MyType_t> v) { /* intentionally empty */ }    // parameter by-val

void g(const std::vector<MyType_t>& v) {
    //...
    f(v);    // <-- this line raises segmentation fault!!! 
             // (works fine if parameter of f() is by-ref instead of by-val)
    //...
}

当我检查调试器时,我发现它来自sVal的{​​{1}}数据成员。

上面的代码没有重现问题,所以我不希望得到任何具体的答案。然而,我确实感谢建议让我更接近找到它的来源。

以下是一些背景信息: 我有一个逻辑表达式解析器。表达式可以包含算术表达式,后期绑定变量和函数调用。给定这样的表达式,解析器创建一个具有多种类型节点的树。解析阶段总是成功的。我在评估阶段遇到了问题。

评估阶段对树结构具有破坏性,因此其节点被恢复&#34;在每次评估之前使用备份副本。第一次评估也始终成功。我在下面的评估中遇到了问题,并带有一些特定的表达方式。即使这样,错误也不一致。

请假设我没有内存泄漏和双重释放。 (我使用全局new()重载来跟踪分配/解除分配。)

关于我应该如何解决这个问题的任何想法?

2 个答案:

答案 0 :(得分:2)

  

f(v); // <-- this line raises segmentation fault!!!

此行只有两种方式可以触发SIGSEGV

  1. 你已经用完了堆栈(这对于深度递归过程来说有点常见)。您可以执行ulimit -s unlimited,看看问题是否消失。
  2. 您已损坏v,因此其复制构造函数会触发SIGSEGV
  3.   

    请假设我没有内存泄漏和双重释放。

    有多种许多方法来破坏堆(例如,通过溢出堆块)而不是双重释放。很可能(如果你没有堆栈溢出)valgrind或AddressSanitizer会直接指出你的问题。

答案 1 :(得分:2)

  

当我检查调试器时,我发现它[segfault]来自sVal的{​​{1}}数据成员。

好了,既然你实际上已经添加了大多数的相关代码,我们就会明白为什么在没有/没有足够代码的情况下寻求帮助,因为你只知道其余的A-OK,是一个可怕的想法:

问题1

MyType_t

AWOOGA! explicit MyType_t(std::string sVal_) : Type{DataType::VCHAR}, isNull{sVal.empty()}, sVal{sVal_}, dVal{0} { } 的初始化工具是在您初始化之前检查成员isNull是否为sVal来自参数empty()sVal。 (注意虽然在这种情况下它们是相同的,但真正的顺序是声明,而不是初始化。)您可能认为sVal_将始终设置为isNull,因为它总是检查最初的默认值 - 构造的空成员true,而不是输入sVal ...

但它比那更糟糕!成员不是默认构造的,因为编译器知道这是无意义的,因为关于是复制构造的。因此,在调用sVal_you are acting on an uninitialised variable, and that's undefined behaviour

(旁白:这证明了我的首选命名约定,empty()。偶然输入2个字符比忘记尾随下划线要困难得多。但是如果有的话,人们通常会在中包含尾随下划线。成员,而不是论据。无论如何!)

但是等等!

问题2

虽然m_sVal VCHAR变体的第一个构造函数保证了UB,但是你的第二个强烈邀请它:

MyType_t

在此版本中,您允许从空指针构造explicit MyType_t(const char* psz) : Type{DataType::VCHAR}, isNull{psz ? true : false}, sVal{psz}, dVal{0} { } 。但是让我们看一下http://en.cppreference.com/w/cpp/string/basic_string/basic_string - 其中sVal构造函数采用std::string,(4)和(5),后者是这里调用的构造函数,注释如下:

  

5)构造字符串,其内容使用char const *指向的以null结尾的字符串的副本进行初始化。字符串的长度由第一个空字符决定。 行为未定义,如果s未指向至少sTraits::length(s)+1元素的数组,包括{{1}时的情况是一个空指针。

这是在C ++ 11标准21.4.2.9中形式化的:https://stackoverflow.com/a/10771938/2757035

值得注意的是,CharT 使用的GCC s在传递libstdc++时会抛出异常:“what():basic_string :: _ S_construct null无效“。我可能会从您提到的g++中推断出您正在使用nullptr - 在这种情况下,这可能不是您的问题,除非您使用的是旧版本而不是我。尽管如此,仍然应该避免单纯的可能性。因此,请按照以下步骤保护您的初始化:

gdb

结果

从这时起,由于UB,任何事情都可能发生 - 在你的程序中的任何一点。但UB的常见“默认行为”是段错误。

构建器使用g++,即使对sVal{psz ? psz : ""} 的无效调用完成,std::string也不会根据未初始化的.empty()获得“随机”值:相反,因为它是从UB构造的,isNull是未定义的,并且它的测试可能被删除或内联为一些任意常量,可能导致错误的代码路径被采用,或者正确的。因此,虽然.size()最终获得有效值 ,但isNull却没有。

如果传递的指针为空,构造函数将使用sVal,您的成员isNull 本身将被UB跟随。如果是这样,char const *就没问题,但任何使用std::string sVal的尝试都是未定义的。

在这两种情况下,可能会发生任何数量的不可知事情,因为UB参与其中。如果您想确切知道在这种情况下发生了什么,请反汇编您的程序。

isNull通过引用传递时不发生段错误的原因是模糊的,并且几乎没有话语价值;在任何一种情况下,你都是从sVal元素中读取或复制构造,这些元素的构造以某种方式涉及UB。

解决方案

重点是你需要修复这两个错误的,生成UB的代码片段并确定它是否能解决错误。它很可能是因为第一个和可能第二个构造函数当前正在做的是所以 UB,在实际意义上,你的程序几乎可以保证崩溃某处

必须现在梳理你的程序以解决任何此类编码错误并消除每一个错误,或者最终抓住你,“在足够长的时间线上”