在我的程序中的某个时刻,我在下面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()重载来跟踪分配/解除分配。)
关于我应该如何解决这个问题的任何想法?
答案 0 :(得分:2)
f(v); // <-- this line raises segmentation fault!!!
此行只有两种方式可以触发SIGSEGV
:
ulimit -s unlimited
,看看问题是否消失。v
,因此其复制构造函数会触发SIGSEGV
。请假设我没有内存泄漏和双重释放。
有多种许多方法来破坏堆(例如,通过溢出堆块)而不是双重释放。很可能(如果你没有堆栈溢出)valgrind或AddressSanitizer会直接指出你的问题。
答案 1 :(得分:2)
当我检查调试器时,我发现它[segfault]来自
sVal
的{{1}}数据成员。
好了,既然你实际上已经添加了大多数的相关代码,我们就会明白为什么在没有/没有足够代码的情况下寻求帮助,因为你只知道其余的A-OK,是一个可怕的想法:
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个字符比忘记尾随下划线要困难得多。但是如果有的话,人们通常会在中包含尾随下划线。成员,而不是论据。无论如何!)
但是等等!
虽然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
未指向至少s
个Traits::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,在实际意义上,你的程序几乎可以保证崩溃某处
你必须现在梳理你的程序以解决任何此类编码错误并消除每一个错误,或者将最终抓住你,“在足够长的时间线上”