偶发性崩溃,回溯显示析构函数调用在奇怪的行

Sporadic crash where backtrace shows destructor call at strange line

本文关键字:函数调用 析构 崩溃 回溯 显示 偶发性      更新时间:2024-05-23

我有一个程序通常运行得很好,但今天它在启动时崩溃了。之后立即再次运行它效果很好,所以不幸的是,我无法得到一个最小的例子。然而,代码看起来像这样:

#include "Configuration.hpp"
#include "Program.hpp"

int main()
{
ConfigurationReader confReader; // this is line 6, where gdb indicates a 
// segfault in the *destructor* of 
// ConfigurationReader
confReader.readConf();
Conf & conf = confReader.getConf();
Program program(conf);
program.run();

return 0;
}

该程序报告了一个segfault,并在gdb中提到核心,它说segfault发生在上面代码示例中的第6行,即ConfigurationReader析构函数中。

当然,在这里调用析构函数是没有意义的,因为只有一个ConfigurationReader的实例在四处浮动,它不应该进行析构函数,直到它在main结束时超出范围。即使进行了积极的优化,它也不可能在program破坏之前进行破坏,因为program被赋予了对confReader内部某些东西的引用。

问题:这里发生了什么(或可能发生了什么(?是不是有一些我没有看到的不明确的行为?gdb的堆栈跟踪是否错误?我应该怀疑构建过程出了问题吗?

注意:我知道最好不要让ConfigurationReader拥有它读取的Conf实例,但这不是这个问题的意义所在。请不要在没有回答实际问题的情况下就告诉我这样做。

更新:正如John在评论中指出的,我也应该在这里提供一些关于ConfigurationReader::getConf的信息:

class ConfigurationReader
{
private:
Conf conf;
public:
// ...
Conf & getConf() { return conf; }
}

更新2:我删除了行号以使代码示例可复制,并添加了一条注释,指示gdb显示被调用的析构函数的位置。

注意2:正如我最初所说,不幸的是,我无法提供一个最小的可复制示例。我无法在玩具程序中重现这个问题。我甚至无法在real程序中重现这个问题;这场车祸只发生过一次。

我实际上能够在这里解决我自己的问题,部分是为了回答PaulMcKenzie在评论中的问题。举例来说,这里有一个玩具程序:

class C { };
class D
{
public:
~D()
{
int * p = nullptr;
int x = *p;
}
};
class E { };
class F { };
int main(int, char **)
{
C c;
D d; // this is line 21
E e;
F f;
return 0;
}

当然,当d进行析构函数时,它会在main()结束时崩溃,因为我有意在析构函数中放入一个segfault。

如果我用调试符号构建它,然后在gdb中运行它,我会得到以下回溯:

#0  0x00005555555546cc in D::~D (this=0x7fffffffde67, __in_chrg=<optimized out>) at program.cpp:10
#1  0x000055555555469a in main () at program.cpp:21

注意它在堆栈帧1中报告的行号。当然,实际上第22、23、24、25和26行已经完成,如果需要,我们可以通过登录来证明这一点。

显然,gdb(或工具中负责将行号与编译器输出相关联的任何部分(已经决定,当对象超出范围时,析构函数调用应该与最终导致它们的实际代码行相关联,即构建对象的行。

所以大概我的程序中发生的事情是程序完成了,而崩溃实际上发生在运行中比指示的要晚得多。(这是它自己的问题,因为program.run()是一个没有退出条件的无限循环。由于ConfigurationReader的析构函数本来是不可访问的,而且以前从未运行过,所以它不能正常工作也就不足为奇了。(

仅凭您发布的代码是不可能找出实际问题所在的。因此,这不是您问题的答案,而是帮助您使用gdb自己找到解决方案的一种方法。不过,作为一个评论有点大。

在gdb中运行程序,并在main开始时停止。可以使用start命令。现在在程序的最后一条指令处添加一个断点。您可以使用b _exit在gdb中执行此操作。如果问题没有发生,您的程序应该只命中此断点。在gdb中添加断点时,会得到一个标识该断点的数字。假设_exit中的断点是断点2。您可以添加";命令";在gdb中,应该在遇到特定断点时自动运行。使用commands 2将命令添加到断点2,键入run(添加run命令(,按ENTER键,然后键入end(完成输入命令(。现在键入run开始运行您的程序。

这样,您的程序将在gdb中运行,如果没有出现错误,它将在到达最后一条指令时从头开始再次运行。当错误最终发生时,gdb将停止执行,您可以使用实际进程(而不仅仅是核心转储(来调查问题。