代码之家  ›  专栏  ›  技术社区  ›  Martijn Courteaux

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

  •  6
  • Martijn Courteaux  · 技术社区  · 16 年前

    我正在开发一个游戏,当我在游戏中做一个特定的动作时,它崩溃了。 于是我进行了调试,我看到我的应用程序在简单的C++语句中崩溃了。 if , return , ... 每次我重新运行时,它都会在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 回复  |  直到 16 年前
        1
  •  10
  •   Adam Shiemke    16 年前

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

        2
  •  5
  •   SigTerm    16 年前

    1. 在调试器中运行游戏,在崩溃点检查所有参数的值。使用visualstudio监视窗口或gdb。使用“调用堆栈”检查父例程,尝试思考可能出错的地方。
    2. 在可疑(可能与崩溃有关)例程中,请考虑将所有参数转储到stderr(如果您使用的是libsdl或*nixlike系统),或编写日志文件,或使用(在Windows上)OutputDebugString发送所有错误消息的副本。这将使它们在VisualStudio或调试器的“输出”窗口中可见。您还可以写入“跟踪”(日志(“调用了函数%s”,\u函数\u))
    3. 如果不能立即调试,则在崩溃时生成核心转储。在windows上可以使用MiniDumpWriteDump来完成,而在linux上则是在配置变量的某个地方设置的。核心转储可以由调试器处理。我不确定VS-express是否可以在Windows上处理它们,但是您仍然可以使用WinDBG调试它们。
    4. 如果类内发生崩溃,请检查*此参数。它可能无效或为零。
    5. 如果这个bug真的很邪恶(多线程应用程序中难以捉摸的堆栈损坏会导致延迟崩溃),那么编写自定义内存管理器,它将覆盖new/delete,提供malloc的替代方法(如果你的应用程序出于某种原因使用它,这是可能的),并使用VirtualProtect(windows)或操作系统特定的替代方法锁定所有未使用的内存。在这种情况下,所有潜在的危险操作都将使应用程序立即崩溃,这将允许您调试问题(如果您有及时的调试器)并立即找到危险的例程。我更喜欢这样的“自定义内存管理器”而不是boundschecker之类的——因为在我的经验中,它更有用。作为替代,您可以尝试使用valgrind,它仅在linux上可用。请注意,如果您的应用程序非常频繁地分配内存,您将需要大量的RAM才能锁定每个未使用的内存块(因为要锁定,块的大小应该是页大小字节)。
    6. 如果您发现了一个有问题的例程,请使用调试器的单步执行/单步执行来遍历它。注意论点。
    7. 如果您已经确定了一个有问题的例程,但是由于任何原因不能直接调试它,那么在该例程中的每个语句之后,将所有变量转储到stderr或logfile(fprintf或iostreams-您的选择)。然后分析输出并思考它是如何发生的。确保每次写入后都刷新日志文件,否则可能会在崩溃前丢失数据。

    一般来说,你应该高兴的应用程序崩溃的地方。崩溃意味着您可以使用调试器快速找到一个bug并将其清除。不使程序崩溃的bug要困难得多(一个真正复杂的bug的例子:给定100000个输入值,在数千个输出值中,经过几百次值操作后,app产生一个绝对不正确的结果,这根本不应该发生)

    所以我喜欢Java。。。

    对不起,如果你不能处理语言问题,那完全是你的错。如果你不能使用这个工具,要么选择另一个,要么提高你的技能。顺便说一下,用java制作游戏是可能的。

        3
  •  5
  •   Stephane Rolland    12 年前

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

    大多数情况下,堆栈损坏都是由于“off-by-one错误”造成的。
    堆损坏是因为new/delete没有被小心处理,比如double delete。

    基本上,溢出/损坏会覆盖一条重要的指令,然后在很长时间之后,当您尝试执行该指令时,它将崩溃。

        4
  •  4
  •   Jordan    16 年前

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

    除了这两件事,你可以尝试使用调试器,如visualstudio或Eclipse等。。。

        5
  •  3
  •   Luca Matteis    16 年前

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

        6
  •  2
  •   Puppy    16 年前

    没有简单的C++语句。如果只是简单的条件,你评估。返回只和返回的表达式一样简单。

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

        7
  •  1
  •   kist    16 年前

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

        8
  •  1
  •   BlueRaja - Danny Pflughoeft    16 年前

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

    std::vector strcpy 基于char[]的字符串缺少结尾 '\0' (您应该使用 std::string memcpy (应该使用复制构造函数!),等。

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

        9
  •  1
  •   Zan Lynx    16 年前

    那你就得倒过来看看是怎么回事。内存更改的硬件断点是 非常

    在Linux/BSD/Mac上,使用GDB的脚本特性可以提供很多帮助。您可以编写脚本,这样在断点被命中20次之后,它就可以对数组元素17的地址进行硬件监视。

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

        10
  •  1
  •   Community Mohan Dere    9 年前

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

        11
  •  1
  •   jackr    16 年前

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

        12
  •  0
  •   Paul Nathan    16 年前

    检查指针。很可能,您正在取消对空指针的引用。

        13
  •  0
  •   Diego Pereyra    16 年前

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

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

        14
  •  0
  •   adhanlon    16 年前

        15
  •  -1
  •   Klaim    16 年前

    Refactoring.

    扫描所有的代码,如果第一次读的时候不清楚的话,让它更清楚,试着理解你写的东西,并立即修复那些看起来不正确的东西。

    推荐文章