C ++:当我的应用程序在随机位置崩溃时从哪里开始?

时间:2010-07-07 18:17:46

标签: c++ debugging crash

我正在开发一款游戏,当我在游戏中执行特定操作时,它会崩溃。 所以我进行了调试,我看到我的应用程序崩溃了简单的C ++语句,如ifreturn,...每次重新运行时,它会在3行中的一行中随机崩溃,但从未成功

第1行:

if (dynamic) { ... } // dynamic is a bool member of my class

第2行:

return m_Fixture; // a line of the Box2D physical engine. m_Fixture is a pointer.

第3行:

return m_Density; // The body of a simple getter for an integer.

我从应用程序和操作系统中都没有错误...

是否有提示,提示或技巧可以更有效地调试并了解正在发生的事情?

这就是我喜欢Java的原因......

由于

15 个答案:

答案 0 :(得分:10)

这样的随机崩溃通常是由堆栈损坏引起的,因为这些是分支指令,因此对堆栈的状况很敏感。这些有点难以追踪,但你应该运行valgrind并检查每次崩溃时的调用堆栈,以尝试识别可能是错误根本原因的常见函数。

答案 1 :(得分:5)

这些主要是由于堆栈损坏,但堆损坏也会以这种方式影响程序。

由于“一个错误的关闭”,大多数情况下都会发生堆栈损坏 发生堆损坏是因为没有仔细处理新的/删除,比如双删除。

基本上发生的事情是溢出/损坏会覆盖一条重要的指令,然后很久以后,当你尝试执行指令时,它会崩溃。

答案 2 :(得分:4)

我通常喜欢花点时间退一步思考代码,试图捕捉任何逻辑错误。

您可以尝试注释掉代码的不同部分,看看它是否会影响程序的编译方式。

除了这两件事,你可以尝试使用像Visual Studio或Eclipse等调试器......

最后,您可以尝试发布您的代码以及您在具有知道编程的社区的网站上发生的错误,并且可以帮助您解决错误(读取:stackoverflow)

答案 3 :(得分:4)

  

是否有提示,提示或技巧可以更有效地调试并了解正在发生的事情?

  1. 在调试器中运行游戏,在崩溃点,检查所有参数的值。使用visual studio观察窗口或使用gdb。使用“调用堆栈”检查父例程,尝试思考可能出现的问题。
  2. 在可疑(可能与崩溃相关)例程中,考虑将所有参数转储到stderr(如果您正在使用libsdl或在* nixlike系统上),或者编写日志文件,或使用所有错误消息发送重复项(在Windows上)OutputDebugString。这将使它们在visual studio或调试器的“输出”窗口中可见。你也可以写“痕迹”(log(“函数%s被称为”,__ FUNCTION__))
  3. 如果无法立即调试,请在崩溃时生成核心转储。在Windows上,它可以使用MiniDumpWriteDump来完成,在linux上它设置在配置变量的某个地方。核心转储可以由调试器处理。我不确定VS express是否可以在Windows上处理它们,但你仍然可以使用WinDBG调试它们。
  4. 如果在类中发生崩溃,请检查*此参数。它可能无效或为零。
  5. 如果这个bug真的很邪恶(多线程应用程序中难以理解的堆栈损坏导致延迟崩溃),编写自定义内存管理器,将覆盖new / delete,提供malloc的替代方案(如果你的应用程序出于某种原因)使用它,这可能是),并使用VirtualProtect(windows)或特定于操作系统的替代方法锁定所有未使用的内存。在这种情况下,所有潜在危险的操作都会立即使应用程序崩溃,这将允许您调试问题(如果您有即时调试程序)并立即找到危险的例程。我更喜欢这样的“自定义内存管理器”来限制搜索等等 - 因为根据我的经验,它更有用。作为替代方案,您可以尝试使用valgrind,它仅在Linux上可用。请注意,如果您的应用程序经常分配内存,您将需要大量的RAM才能锁定每个未使用的内存块(因为要锁定,块应该是PAGE_SIZE字节大)。
  6. 在需要健全性检查的区域,要么使用ASSERT,要么(IMO更好的解决方案)编写一个会使应用程序崩溃的例程(通过使用有意义的消息抛出std :: exception),如果某些条件不是'见过面。
  7. 如果您确定了一个有问题的例程,请使用调试器的步骤进入/跳过它。观看论点。
  8. 如果您确定了一个有问题的例程,但由于某种原因无法直接调试它,则在该例程中的每个语句之后,将所有变量转储到stderr或logfile(fprintf或iostreams - 您的选择)。然后分析输出并思考它是如何发生的。确保在每次写入后刷新日志文件,否则您可能会在崩溃之前错过数据。

一般来说,你应该对应用程序在某处崩溃感到高兴。崩溃意味着您可以使用调试器快速找到并消灭的错误。不会使程序崩溃的错误要困难得多(真正复杂的错误的例子:给定100000个输入值,经过数百次操作值,在数千个输出中,app产生1个绝对错误的结果,不应该有发生了一切)

  

这就是我喜欢Java的原因......

打扰一下,如果你不能处理语言,那完全是你的错。如果您无法处理该工具,请选择另一个工具或提高您的技能。顺便说一句,可以用java制作游戏。

答案 4 :(得分:3)

当您访问不允许访问的内存位置或尝试以不允许的方式访问内存位置时(例如,尝试写入只读方式),通常会发生崩溃/ Seg故障位置)。

有许多内存分析器工具,例如我使用Valgrind,这非常适合告诉问题是什么(不仅是行号,还有导致崩溃的原因)。

答案 5 :(得分:2)

没有简单的C ++语句。 if只是与您评估的条件一样简单。返回只是与您返回的表达式一样简单。

您应该使用调试器和/或发布一些崩溃的代码。 “我的应用程序崩溃”作为信息无法使用。

答案 6 :(得分:1)

之前我遇到过这样的问题。我试图从不同的线程刷新GUI。

答案 7 :(得分:1)

如果if语句涉及解引用指针,那么你几乎肯定会破坏堆栈(这解释了为什么无辜的return 0会崩溃......)

例如,这可能发生在数组中超出范围(您应该使用std::vector!),尝试strcpy基于char []的字符串缺少结尾{{ 1}}(您应该使用'\0'!),将错误的大小传递给std::string(您应该使用复制构造函数!)等。

尝试找出一种可靠地再现它的方法,然后将手表放在损坏的指针上。运行代码逐行,直到找到破坏指针的行。

答案 8 :(得分:1)

看一下反汇编。几乎所有的C / C ++调试器都会很高兴向您展示机器代码和程序崩溃的寄存器。寄存器包括指令指针(x86 / x64上的EIP或RIP),它是程序停止时的位置。其他寄存器通常具有存储器地址或数据。如果内存地址为0或指针错误,则存在问题。

然后你只需要向后工作就可以了解它是如何形成的。关于内存更改的硬件断点在这里非常非常有用。

在Linux / BSD / Mac上,使用GDB的脚本功能可以在这里提供很多帮助。您可以编写脚本,以便在断点被击中20次后,可以在数组元素17的地址上进行硬件监视。等等。

您也可以在程序中编写调试程序。使用assert()函数。到处!

使用assert检查每个函数的参数。在退出函数之前,使用assert检查每个对象的状态。在游戏中,断言玩家在地图上,玩家的健康状况在0到100之间,断言你能想到的一切。对于复杂的对象,将verify()或validate()函数写入对象本身,检查有关它的所有内容,然后从assert()中调用它们。

编写调试的另一种方法是让程序在Linux中使用signal()或在Windows中使用asm int 3从程序中进入调试器。然后,您可以将临时代码写入程序,以检查它是否在主循环的迭代1117321上。如果错误总是发生在1117322,这可能很有用。程序执行速度比使用调试器断点要快得多。

答案 9 :(得分:1)

一些提示:
- 在调试器下运行应用程序,将符号文件(PDB)放在一起 - How to set Visual Studio as the default post-mortem debugger?
- 为WinDbg设置默认调试器Just-in-time Debugging
- 检查内存分配Overriding new and deleteOverriding malloc and free

答案 10 :(得分:1)

另一个技巧:关闭代码优化并查看崩溃点是否更有意义。允许优化将代码中的一小部分浮动到令人惊讶的地方;回溯到源代码行的映射可能不够完美。

答案 11 :(得分:0)

检查指针。猜测,你正在取消引用一个空指针。

答案 12 :(得分:0)

当有一些对已删除对象的引用时,我发现'随机'崩溃了。由于内存不一定被覆盖,在许多情况下你没有注意到它并且程序正常工作,并且在内存更新后崩溃并且不再有效。

只是为了调试目的,尝试评论一些可疑的'删除'。然后,如果它不再崩溃,那么你就是。

答案 13 :(得分:0)

使用GNU调试器

答案 14 :(得分:-1)

Refactoring.

扫描所有代码,如果在第一次阅读时不清楚则更清楚,尝试理解您所写的内容并立即修复看似不正确的内容。

你肯定会以这种方式发现问题并解决很多其他问题。