【问题标题】:assert and NDEBUG断言和 NDEBUG
【发布时间】:2010-09-28 04:47:06
【问题描述】:

在阅读了一些关于滥用异常的线程之后(基本上是说如果函数的先决条件不正确,您不想展开堆栈 - 可能表明您的所有内存都已损坏或同样危险的东西)我正在考虑使用 assert () 更频繁。以前我只使用 assert() 作为调试工具,我认为这就是很多 C++ 程序员使用它的方式。我担心我的部分错误处理会被未来某个时候引入运行时构建的 NDEBUG #define 关闭。有没有办法解决这个问题并且其他人对此有疑问(即我应该担心它)吗?

谢谢, 帕特

编辑: 我正在阅读的线程的要点是,如果您的应用程序真的被窃听,那么展开堆栈可能会损坏系统,例如,如果析构函数将某些内容写入文件并且文件句柄已损坏。我不建议使用 assert 进行正常的错误处理。我目前的用例很弱,但看看你的想法:

//check later code won't crash the system if( buf.length() % 2 ) return false; // do other stuff that shouldn't affect bufs length //copy 2 bytes into buf at a time, if length is odd then don't know //what will happen so use assert to make sure it can't damage anything assert( !(buf.length() % 2) ); for( i = 0; i != buf.length(); i += 2 ) memcpy( buf + i, data, 2 );

edit2:讨论在这里: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/80083ac31a1188da

【问题讨论】:

    标签: c++ error-handling


    【解决方案1】:

    我喜欢定义自己的断言宏。我做了两个 -- ASSERT 测试总是(即使是优化的构建),DASSERT 只对调试构建有影响。您可能希望默认为 ASSERT,但如果某些东西的测试成本很高,或者性能敏感区域的内部循环内的断言可以更改为 DASSERT。

    另外,请记住,断言只能用于完全无意义的情况,这些情况表明程序中存在逻辑错误并且您无法从中恢复。这是对您的编程正确性的测试。绝不应使用断言来代替错误处理、异常或健壮性,并且您绝不应断言与格式错误或不正确的用户输入相关的任何内容——此类事情应妥善处理。断言只是一种受控崩溃,您可以在其中输出一些额外的调试信息。

    这是我的宏:

    /// ASSERT(condition) checks if the condition is met, and if not, calls
    /// ABORT with an error message indicating the module and line where
    /// the error occurred.
    #ifndef ASSERT
    #define ASSERT(x)                                                      \
        if (!(x)) {                                                         \
            char buf[2048];                                                 \
            snprintf (buf, 2048, "Assertion failed in \"%s\", line %d\n"    \
                     "\tProbable bug in software.\n",                       \
                     __FILE__, __LINE__);                                   \
            ABORT (buf);                                                    \
        }                                                                   \
        else   // This 'else' exists to catch the user's following semicolon
    #endif
    
    
    /// DASSERT(condition) is just like ASSERT, except that it only is 
    /// functional in DEBUG mode, but does nothing when in a non-DEBUG
    /// (optimized, shipping) build.
    #ifdef DEBUG
    # define DASSERT(x) ASSERT(x)
    #else
    # define DASSERT(x) /* DASSERT does nothing when not debugging */
    #endif
    

    【讨论】:

      【解决方案2】:

      也许我没有正确理解您,但不应在发布代码中使用 IMO assert() 来执行您所描述的操作,即强制执行不变量。

      如果一个函数的前置条件不正确,你会怎么做?您希望如何将其传达给:

      • 交互式用户?
      • 您的支持人员?

      在这两种情况下,您都需要调用一些错误处理代码,并且(在我看来)异常只是票证。

      但是,如果您仍然想走这条路,您可以将以下内容放在每个源文件的顶部:

      #undef NDEBUG
      

      我不推荐这个。请不要这样做。没有人会感谢你:-)

      Seb

      【讨论】:

        【解决方案3】:

        嗯,a failing assertion is a bug,不多也不少。与取消引用空指针相同,只是你自己给了你的软件来打击你。勇敢的决定,你值得称赞!

        通过异常跳出问题几乎没有帮助,它并不能修复错误。所以我建议实现你自己的 ASSERT() 宏,它:

        • 尝试尽可能多地收集有关失败的数据(断言表达式、堆栈跟踪、用户环境等)
        • 努力让用户尽可能轻松地向您举报,并且
        • 弹出一个消息框,为给您带来的不便表示歉意,并粗暴地中止了应用程序。

        如果性能是一个问题,您可以考虑使用某种 SOFT_ASSERT() 宏,该宏会从发布版本中消失。

        【讨论】:

        • 第 4 步:将数据发布到您的服务器并自动打开一个错误。
        【解决方案4】:

        我已经看到程序实现了自己的断言和验证宏。在调试模式下使用断言并在调试和发布模式下验证。具体来说,验证可用于在检查失败时从程序中优雅地退出(或者比彻底崩溃更优雅)。可能是我见过的第一个也是最好的用途之一是在 Unreal 代码中(我相信您仍然可以查看 Unreal 标头)。

        【讨论】:

          【解决方案5】:

          除了在调试期间检查假设的随意方式之外,我会避免依赖断言。在发布代码中,您重新定义的断言将由随机崩溃处理,这对用户不友好

          如果在调试过程中,您的假设(例如给定条件永远不会为真)被证明是无效的。相应地更改错误处理代码以考虑该条件。

          否则,请使用更加用户友好的工具来处理违反假设的条件。创建一个名为 CAssumptionViolated 的异常,将它扔到您要断言的地方。在您的主程序中捕获它,并为用户提供一种提醒您错误的方法。更好的是,提供异常的调试信息。更好的是,以某种方式自动将调试信息转发给您的组织。

          【讨论】:

          • 我读到的线程的重点是,如果您的应用程序真的被窃听,那么展开堆栈可能会损坏系统,例如,如果析构函数向文件写入内容并且文件句柄已损坏。
          • 如果您的“断言”检查无效的文件句柄,当无效时将文件句柄标记为无效(可能设置一个标志告诉您的代码不要使用它)然后抛出异常。然后您的析构函数标志可以在写入之前检查该标志。
          • 不,断言和文件句柄是分开的。如果断言检测到发生了“不可能”的事情(而不是意外),那么您可以(应该?)假设您不能信任任何事情,包括堆栈展开到一些不错的日志记录/错误报告功能。
          【解决方案6】:

          您可以构建自己的断言,而不是使用常用的 C assert.h。您的断言不会被禁用。

          看看 assert() 是如何在 /usr/include/assert.h (或任何地方)中实现的。这只是一些最终调用“断言失败”函数的预处理器魔法。

          在我们的嵌入式环境中,我们一直替换 assert()。

          【讨论】:

          • 另外你可能不喜欢厂商的assert()实现,所以我们通常会定制你自己的assert()。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-04-14
          • 1970-01-01
          • 2011-01-18
          • 1970-01-01
          • 2019-02-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多