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

Posted

技术标签:

【中文标题】C++:当我的应用程序在随机位置崩溃时从哪里开始?【英文标题】:C++: Where to start when my application crashes at random places? 【发布时间】:2011-03-12 23:26:48 【问题描述】:

我正在开发一款游戏,当我在游戏中执行特定操作时,它会崩溃。 所以我去调试了,我看到我的应用程序在简单的 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 的原因...

谢谢

【问题讨论】:

如果它在 if 语句上崩溃,可能参与if 条件的变量之一是空指针。 当游戏崩溃时,您是否收到任何错误消息(来自您的应用程序或操作系统)?你能可靠地重现这些崩溃吗? 如果您使用的是第三方库,请确保您针对正确的头文件进行编译。如果没有,程序可能会崩溃,如果还不够,调试文件中可能会出现一些不匹配,最终您会看到不正确的“错误行”。 发布一些崩溃的代码。你在什么平台上调试?如果它具有反汇编模式,请准确找出它崩溃的指令(以及原因)。 看起来您的this 指针无效.. 【参考方案1】:

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

【讨论】:

如何找到崩溃的日志部分? @martijn:我不确定你的意思。您需要自己创建一个日志文件,所以它会放在您放置的任何位置。每当发生“崩溃”时,通常都会生成堆栈跟踪。你用什么调试器?【参考方案2】:

是否有提示、提示或技巧可以提高调试效率并了解发生了什么?

    在调试器中运行游戏,在崩溃点检查所有参数的值。使用 Visual Studio 监视窗口或使用 gdb。使用“调用堆栈”检查父例程,尝试思考可能出现的问题。 在可疑(可能与崩溃有关)例程中,考虑将所有参数转储到 stderr(如果您使用 libsdl 或在 *nixlike 系统上),或编写日志文件,或使用(在 Windows 上)发送所有错误消息的副本输出调试字符串。这将使它们在 Visual Studio 或调试器的“输出”窗口中可见。你也可以写"traces" (log("function %s was called", __FUNCTION__)) 如果您无法立即调试,请在崩溃时生成核心转储。在 Windows 上可以使用 MiniDumpWriteDump 完成,在 linux 上它设置在配置变量中的某个位置。核心转储可以由调试器处理。我不确定 VS express 是否可以在 Windows 上处理它们,但您仍然可以使用 WinDBG 调试它们。 如果在类内发生崩溃,请检查 *this 参数。它可能无效或为零。 如果错误确实是邪恶的(多线程应用程序中难以捉摸的堆栈损坏导致延迟崩溃),请编写自定义内存管理器,它将覆盖新/删除,提供 malloc 的替代方案(如果您的应用程序出于某种原因使用它,可能),并且使用 VirtualProtect(Windows)或特定于操作系统的替代方案锁定所有未使用的内存内存。在这种情况下,所有潜在的危险操作都会立即使应用程序崩溃,这将允许您调试问题(如果您有即时调试器)并立即找到危险的例程。我更喜欢这种“自定义内存管理器”而不是边界检查器等 - 因为根据我的经验,它更有用。作为替代方案,您可以尝试使用 valgrind,它仅在 linux 上可用。请注意,如果您的应用程序非常频繁地分配内存,您将需要大量 RAM 才能锁定每个未使用的内存块(因为要被锁定,块应该是 PAGE_SIZE 字节大)。 在需要完整性检查的区域中,请使用 ASSERT,或者(IMO 更好的解决方案)编写一个例程,如果不满足某些条件,该例程将使应用程序崩溃(通过抛出带有有意义消息的 std::exception)。 如果您发现了有问题的例程,请使用调试器的单步执行/单步执行遍历它。观看争论。 如果您发现了一个有问题的例程,但由于某种原因无法直接对其进行调试,则在该例程中的每个语句之后,将所有变量转储到 stderr 或日志文件(fprintf 或 iostreams - 您的选择)。然后分析输出并思考它是如何发生的。确保在每次写入后刷新日志文件,否则您可能会在崩溃前丢失数据。

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

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

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

【讨论】:

【参考方案3】:

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

堆栈损坏大部分时间是由于“关闭一个错误”而发生的。 堆损坏是因为没有仔细处理 new/delete,比如双重删除。

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

【讨论】:

我编辑了你的答案以删除关于空指针删除的错误陈述。【参考方案4】:

我通常喜欢花一秒钟后退一步思考代码,试图找出任何逻辑错误。

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

除了这两件事之外,您还可以尝试使用 Visual Studio 或 Eclipse 等调试器...

最后,您可以尝试在网站上发布您的代码和遇到的错误,该网站的社区了解编程并可以帮助您解决错误(阅读:***)

【讨论】:

+1,有时退后一步思考代码在做什么是找出错误的最佳方法。 发布代码不起作用,因为一些远离实际崩溃站点的代码可能会修改一些它不应该修改的数据,并且不会将其纳入问题。【参考方案5】:

当您访问不允许访问的内存位置,或者您尝试以不允许的方式访问内存位置(例如,尝试写入只读位置)。

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

【讨论】:

【参考方案6】:

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

您应该使用调试器和/或发布一些崩溃代码。将“我的应用程序崩溃”作为信息没有多大用处。

【讨论】:

【参考方案7】:

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

【讨论】:

【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

看反汇编。几乎所有 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 处,这将很有用。程序执行这种方式将比使用调试器断点快得多。

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

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

【讨论】:

【参考方案12】:

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

【讨论】:

不一定。即使指针不为空,事情也会变得有趣。 @SigTerm:“猜测”;也就是说,这是我首先要看的地方。当然,事情可能会变得更丑陋。【参考方案13】:

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

仅出于调试目的,请尝试注释掉一些可疑的“删除”。然后,如果它不再崩溃,那就是你。

【讨论】:

【参考方案14】:

使用 GNU 调试器

【讨论】:

【参考方案15】:

Refactoring.

扫描所有代码,如果第一次阅读不清楚,请使其更清晰,尝试理解你写的内容并立即修复看起来不正确的地方。

您肯定会通过这种方式发现问题并解决许多其他问题。

【讨论】:

以上是关于C++:当我的应用程序在随机位置崩溃时从哪里开始?的主要内容,如果未能解决你的问题,请参考以下文章

当我不知道它可能会抛出哪里时,如何记录异常?

Unity Android build 随机崩溃

当我的应用程序在野外崩溃时,如何提高调试数据的质量?

解决随机崩溃

Android 应用程序随机崩溃(java.lang.NoClassDefFoundError)

当我的 MSVS C++ 应用程序崩溃时,如何避免弹出错误对话框