【问题标题】:Adding message to assert将消息添加到断言
【发布时间】:2011-04-15 15:15:40
【问题描述】:

你好!

我正在寻找一种将自定义消息添加到断言语句的方法。 我发现了这个问题Add custom messages in assert?,但那里的消息是静态的。我想做这样的事情:

assert((0 < x) && (x < 10), std::string("x was ") + myToString(x));

当断言失败时,我想要正常的输出加上例如“x 是 100”。

【问题讨论】:

  • 我觉得这里你有更好的answer
  • 丑陋的黑客:if (fail_condition) assert(!"My message");
  • @MarkKCowan ,我认为您的“丑陋黑客”实际上比“&&”补丁要好得多,因为它只显示消息:)
  • @MarkKCowan 的黑客攻击的一个问题是,一旦您 #define NDEBUG 为您的发布代码(删除断言)您的代码仍将评估潜在的代价高昂的条件吗?编译器会知道优化掉一个空的if吗?

标签: c++ logging debugging assert


【解决方案1】:

你在这里不走运。最好的方法是定义自己的assert 宏。

基本上可以是这样的:

#ifndef NDEBUG
#   define ASSERT(condition, message) \
    do { \
        if (! (condition)) { \
            std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \
                      << " line " << __LINE__ << ": " << message << std::endl; \
            std::terminate(); \
        } \
    } while (false)
#else
#   define ASSERT(condition, message) do { } while (false)
#endif

仅当未定义无调试宏 NDEBUG 时,才会定义 ASSERT 宏。

然后你会这样使用它:

ASSERT((0 < x) && (x < 10), "x was " << x);

这比你的用法简单一点,因为你不需要显式地对"x was "x 进行字符串化,这是由宏隐式完成的。

【讨论】:

  • 为什么 do { } while (false) ?
  • @tauran:所以在使用宏的时候可以在宏后面加分号。
  • 可能没有那么多asm("int 3"),但abort() 可能会更好。
  • @Jon:取决于。请记住,asm 是不可移植的(在 MS 编译器上,甚至不能在 64 版本中工作),而且取决于您运行代码的时间、地点和方式,您可能不想要它触发断点。 (虽然 MS 有 __debugbreak 内在函数,它适用于所有 MS 平台)理想情况下,您可以使宏可配置,因此开发人员可以在编译时或运行时在中止和中断断言之间切换
  • 为什么不直接使用 {} 将其设为代码块?为什么要这样做?
【解决方案2】:

有一些老技巧可以在不编写自己的例程的情况下包含消息:

第一个是这样的:

bool testbool = false;
assert(("this is the time", testbool));

还有:

bool testbool = false;
assert(testbool && "This is a message");

第一个有效,因为内部括号表达式结果是'testbool'的值。 第二个有效,因为字符串的值将不为零。

【讨论】:

  • 请注意,assert((&lt;cond&gt;) &amp;&amp; "msg"); 会导致 CppCheck (v1.90) 生成“incorrectStringBooleanError”警告
  • @ahogen 您可以尝试将字符串转换为 int 或 bool 以查看是否可以使消息安静下来,尽管在这种情况下它可能会抱怨转换。如果您找到解决方法,请告诉我。第一种方法对 CppCheck 是否有效?
【解决方案3】:

一个更好的选择是教调试器在断言失败时停止,然后您不仅可以检查 x 值,还可以检查任何其他信息,包括调用堆栈。也许,这就是你真正想要的。 这里提到了示例实现Ways to show your co-programmers that some methods are not yet implemented in a class when programming in C++

【讨论】:

  • +1 不是我想要的,但有一天会非常有用。
  • 我征服了,尽管您可能不在控制台调试问题。但在这种情况下,我想记录错误,而不仅仅是在屏幕上显示消息。
【解决方案4】:
#define ASSERT_WITH_MESSAGE(condition, message) do { \
if (!(condition)) { printf((message)); } \
assert ((condition)); } while(false)

【讨论】:

  • 这会评估条件两次,这可能会导致条件发生变化:ASSERT_WITH_MESSAGE(++i == 10, "Ten")
【解决方案5】:

扩展康德拉德鲁道夫的回答:

#include <iostream>

#ifdef NDEBUG
#define assert(condition, message) 0
#else
#define assert(condition, message)\
   (!(condition)) ?\
      (std::cerr << "Assertion failed: (" << #condition << "), "\
      << "function " << __FUNCTION__\
      << ", file " << __FILE__\
      << ", line " << __LINE__ << "."\
      << std::endl << message << std::endl, abort(), 0) : 1
#endif

void foo() {
   int sum = 0;
   assert((sum = 1 + 1) == 3, "got sum of " << sum << ", but expected 3");
}

int main () {
   foo();
}

输出是...

Assertion failed: ((sum = 1 + 1) == 3), function foo, file foo.cpp, line 13.
got sum of 2, but expected 3
zsh: abort      ./a.out

这类似于 std::assert 宏在我的系统上输出的内容,只是附加了用户定义的消息

【讨论】:

    【解决方案6】:

    为了完整起见,我在 C++ 中发布了一个插入式 2 文件断言宏实现:

    #include <pempek_assert.h>
    
    int main()
    {
      float min = 0.0f;
      float max = 1.0f;
      float v = 2.0f;
      PEMPEK_ASSERT(v > min && v < max,
                    "invalid value: %f, must be between %f and %f", v, min, max);
    
      return 0;
    }
    

    会提示您:

    Assertion 'v > min && v < max' failed (DEBUG)
      in file e.cpp, line 8
      function: int main()
      with message: invalid value: 2.000000, must be between 0.000000 and 1.000000
    
    Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:
    

    在哪里

    • (I)gnore:忽略当前断言
    • 永远忽略 (F):记住触发断言的文件和行 在程序的剩余执行过程中忽略它
    • 忽略 (A)ll:忽略所有剩余的断言(所有文件和行)
    • (D)ebug:如果已连接,则进入调试器,否则 abort()(在 Windows 上, 系统会提示用户附加调试器)
    • A(b)ort:立即致电abort()

    您可以在此处找到更多信息:

    希望对您有所帮助。

    【讨论】:

      【解决方案7】:

      是的,这是可能的。

      要启用像better_assert((0 &lt; x) &amp;&amp; (x &lt; 10), std::string("x was ") + myToString(x)); 这样的表达式,我们应该有一个相应的宏

      #define better_assert(EXPRESSION, ... ) ((EXPRESSION) ? \
      (void)0 : print_assertion(std::cerr, \
      "Assertion failure: ", #EXPRESSION, " in File: ", __FILE__, \ 
      " in Line: ", __LINE__ __VA_OPT__(,) __VA_ARGS__))
      

      其中print_assertion 是执行断言的代理函数。当EXPRESSION 被评估false 时,所有调试信息__VA_ARGS__ 将被转储到std::cerr。这个函数接受任意数量的参数,因此我们应该实现一个可变参数模板函数:

      template< typename... Args >
      void print_assertion(std::ostream& out, Args&&... args)
      {
          out.precision( 20 );
          if constexpr( debug_mode )
          {
              (out << ... << args) << std::endl;
              abort();
          }
      }
      

      在前面的实现中,表达式(out &lt;&lt; ... &lt;&lt; args) &lt;&lt; std::endl;利用了C++17中的折叠表达式(https://en.cppreference.com/w/cpp/language/fold);常量表达式debug_mode与传递的编译选项有关,可以定义为

      #ifdef NDEBUG
          constexpr std::uint_least64_t debug_mode = 0;
      #else
          constexpr std::uint_least64_t debug_mode = 1;
      #endif
      

      还值得一提的是,表达式if constexpr( debug_mode ) 使用了自 C++17 以来导入的 constexpr if (https://en.cppreference.com/w/cpp/language/if)。

      总结一下,我们有:

      #ifdef NDEBUG
          constexpr std::uint_least64_t debug_mode = 0;
      #else
          constexpr std::uint_least64_t debug_mode = 1;
      #endif
      
      template< typename... Args >
      void print_assertion(std::ostream& out, Args&&... args)
      {
          out.precision( 20 );
          if constexpr( debug_mode )
          {
              (out << ... << args) << std::endl;
              abort();
          }
      }
      #ifdef better_assert
      #undef better_assert
      #endif
      #define better_assert(EXPRESSION, ... ) ((EXPRESSION) ? (void)0 : print_assertion(std::cerr, "Assertion failure: ",  #EXPRESSION, " in File: ", __FILE__, " in Line: ",  __LINE__ __VA_OPT__(,) __VA_ARGS__))
      

      展示其用法的典型测试用例可以是:

      double const a = 3.14159265358979;
      double const b = 2.0 * std::asin( 1.0 );
      better_assert( a==b, " a is supposed to be equal to b, but now a = ", a, " and b = ", b );
      

      这会产生类似的错误信息:

      Assertion failure: a==b in File: test.cc in Line: 9 a is supposed to be equal to b, but now a = 3.1415926535897900074 and b = 3.141592653589793116
      [1]    8414 abort (core dumped)  ./test
      

      完整的源代码可以在这个 repo 中找到:https://github.com/fengwang/better_assert

      【讨论】:

        【解决方案8】:

        为了接受 Feng Wang 的回答,在较新版本的 C++ 中,内联的任何内容都将被优化掉。所以你可以在头文件中有一个内联函数来完成所有的工作。

        inline constexpr void NOT_USED()
        {
        }
        
        template <class T, class ...ARGS>
        inline constexpr void NOT_USED(T && first, ARGS && ...args)
        {
        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wunused-result"
            static_cast<void>(first);
        #pragma GCC diagnostic pop
            NOT_USED(args...);
        }
        
        template<typename ... ARGS>
        void SAFE_ASSERT(bool test_result, ARGS &&... args)
        {
        #ifdef _DEBUG
            if(test_result)
            {
                (std::cerr << ... << args) << std::endl;
                abort();
            }
        #else
            NOT_USED(test_result, args...);
        #endif
        }
        

        一些cmets:

        1. 如果_DEBUG没有定义,函数就会变成空的,所以可以100%优化出来

        2. 如果调用有副作用的函数,非调试代码仍然有效:

           my_assert(c++ < --z, "the #define versions do not behave similarly");
          

          这些副作用在这里清晰可见。但是,如果您调用一个函数,可能真的很难知道函数中是否发生了原本没有预料到的事情。

          尽管如此 (Example),有一些方法可以防止此类副作用的发生。但总而言之,在某些情况下,您需要调用一个函数进行测试,它可能会产生副作用,因此不需要在非调试代码中进行优化。

        3. 我使用abort() 因为我知道可以正确停止调试器,std::terminate() 是现代系统中的 C++ 方式,但如果std::terminate() 不适用于您的调试器,abort() 将.

        4. NOT_USED() 是为了避免关于未使用的函数参数的警告(如果您没有该警告,您可能会遇到预期的错误)。

        5. 我在snapdev 中有一个实现:参见safe_assert.hnot_used.h

        【讨论】:

          【解决方案9】:

          按照 Konrad Rudolf 的回答,您可以更简洁地使用

          #include <assert.h>
          #include <stdio.h>
          #define ASSERT(condition,...) assert( \
              condition|| \
              (fprintf(stderr,__VA_ARGS__)&&fprintf(stderr," at %s:%d\n",__FILE__,__LINE__)) \
          );
          

          这也适用于 C,

          它使用您链接的问题的一些答案中的一般概念来工作,但宏允许它更灵活一些

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-04-17
            • 2020-10-29
            • 2014-02-23
            • 1970-01-01
            • 1970-01-01
            • 2012-08-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多