【问题标题】:Undebuggable non-deterministic heisenbug in single-threaded C++ function call单线程 C++ 函数调用中不可调试的非确定性 heisenbug
【发布时间】:2012-06-26 10:10:08
【问题描述】:

我已经走到了尽头:我有一个单线程 C++ 程序。这里是一些经验数据和背景资料,我尽量突出最重要的关键词;

  • 我正在谈论的整个部分没有任何系统调用,除了标准 C++ 库可能执行的内存(取消)分配调用(@987654322 @s 参与)。这是一个纯粹的逻辑算法。
  • 此行为应该是确定性的,取决于输入,我不会改变。
  • 如果出现错误,程序就会陷入类似于无限循环的状态,似乎开始分配内存超出任何界限
  • 该错误不会表现出来可预测地,我可以从命令行运行程序,有时(可能 30%-50%)错误会表现出来,否则,据我所知,一切运行顺利且正确。
  • 一旦我不是直接从提示符运行程序,而是在 gdb 或 valgrind 中运行程序,错误就消失了,程序永远不会死。
  • 现在最精彩的部分来了:我将问题追溯到(模板化的)非虚拟成员函数调用。就在通话之前,我向std::cout 打印了一条消息,我可以在终端中看到。函数内部的第一行还有一条调试消息,从不显示

我再也看不到任何合理的解释了。也许你可以想出一个如何继续的想法。


编辑:重要的代码行,我更改了行号以便我们可以参考它们并省略了不相关的部分,因此并非所有内容似乎都是最有意义的。

a.cpp

 10     std::set<Array const*>* symbols;
 11     std::set<Array const*> allSymbols;
 12     symbols = &allSymbols;
 //  ... allSymbols are populated with std::inserter
 15     std::cout << "eval; cd = " << &cd << ", cg = " << &cd.cg << std::endl;
 16     senderConstraints = cd.cg.eval(*symbols);

b.cpp

 31     template <typename ArrayContainer>
 32     ConstraintList eval(ArrayContainer const request) {
 33       std::cout << "inside eval ... going to update graph now" << std::endl;

最后一行输出是:

eval; cd = 0x2e6ebb0, cg = 0x2e6ebc0

然后就陷入死循环了。

【问题讨论】:

  • 如果您真的确定这不是竞争条件(效果看起来与您描述的完全一样),我会转储对象的状态以查看导致错误状态的状态。一些错误的初始化顺序也可能导致这种行为。
  • 代码会有所帮助,否则就不可能说出任何事情。然而,“调试器中的错误神奇地消失了”通常指向if(ptr != NULL) do_something(); 的星座和一个未初始化的变量。如果内存位置意外为零,则不会发生错误,否则会发生。调试器将 所有 变量(即使是您没有初始化的变量)初始化为零,因此该错误“神奇地消失了”。
  • 您可以尝试使用strace 跟踪系统调用(有些可能隐藏在系统库中)。还可以考虑添加 lots 的调试打印,越多越好。
  • 您的程序可能隐式依赖于一些未初始化的堆栈变量,而 Valgrind 无法捕捉到这些变量。
  • @MareInfinitus:好吧,如果我只有一个线程并且除了给我堆内存的内核部分之外不真正与任何其他东西通信,这怎么可能是一种竞争条件?

标签: c++ debugging object-lifetime heap-corruption non-deterministic


【解决方案1】:

我敢打赌,当您更改时,会打印第二行

ConstraintList eval(ArrayContainer const request)

ConstraintList eval(ArrayContainer const & request)

如果是这样,allSymbols 的状态在第 12 行和第 15 行之间已损坏,或者您的代码看起来更像这样:

std::set<Array const*>* symbols;
{
    std::set<Array const*> allSymbols;
    symbols = &allSymbols;
    //  ... allSymbols are populated with std::inserter
}
std::cout << "eval; cd = " << &cd << ", cg = " << &cd.cg << std::endl;
senderConstraints = cd.cg.eval(*symbols);

这是UB,因为符号指的是一个已经被破坏的对象。

【讨论】:

  • 是的,我是盲人,对不起。不过猜得不错。但在我看来,实际情况看起来要复杂一些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多