自定义C++断言宏

Ste*_* Lu 27 c++ macros assert

我偶然发现了一篇内容丰富的文章:http://cnicholson.net/2009/02/stupid-c-tr​​icks-adventures-in-assert/ ,它指出了我当前的调试宏套件中存在的大量问题.

如果您点击链接,则会在文章末尾附近给出宏的最终版本的完整代码.

提出的一般形式是这样的(如果我在转置它时错误,请有人纠正我):

#ifdef DEBUG
#define ASSERT(cond) \  
    do \  
    { \  
        if (!(cond)) \  
        { \  
            ReportFailure(#cond, __FILE__, __LINE__, 0); \
            HALT(); \
        } \  
    } while(0)  
#else  
#define ASSERT(cond) \  
    do { (void)sizeof(cond); } while(0) 
Run Code Online (Sandbox Code Playgroud)

在考虑使用我所学的内容修改代码时,我注意到该文章的评论中发布了一些有趣的变体:

一个是你不能将这个宏与三元运算符(即cond?ASSERT(x):func())一起使用,并且建议if()用三元运算符和一些括号以及逗号运算符替换它.后来另一位评论者提供了这样的:

#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif
Run Code Online (Sandbox Code Playgroud)

&&在这种情况下,我认为使用逻辑并且特别聪明,在我看来,这个版本比使用if甚至三元的版本更灵活?:.更好的是返回值assert_handler可用于确定程序是否应该停止.虽然我不确定它为什么(HALT(), 1)而不仅仅是HALT().

我忽略了第二个版本是否存在任何特殊缺点?它消除了do{ } while(0)围绕宏的问题,但这里似乎没有必要,因为我们不需要处理ifs.

你怎么看?

AnT*_*AnT 28

在C和C++标准库中,assert是一个充当函数所需的宏.该要求的一部分是用户必须能够在表达式中使用它.例如,assert我可以做标准

int sum = (assert(a > 0), a) + (assert(b < 0), b);
Run Code Online (Sandbox Code Playgroud)

这在功能上是相同的

assert(a > 0 && b < 0)
int sum = a + b;
Run Code Online (Sandbox Code Playgroud)

即使前者可能不是编写表达式的非常好的方法,但在许多更合适的情况下,这个技巧仍然非常有用.

这立即意味着如果想要自己的自定义ASSERT宏来模仿标准assert行为和可用性,那么使用ifdo { } while (0)在定义中ASSERT是不可能的.一种情况仅限于表达式,即使用?:运算符或短路逻辑运算符.

当然,如果一个人不关心制作类似标准的习惯ASSERT,那么人们可以使用任何东西,包括if.链接的文章似乎甚至没有考虑这个问题,这很奇怪.在我看来,类似函数的断言宏肯定比非函数式宏更有用.

至于(HALT(), 1)...它是这样做的,因为&&运算符需要一个有效的参数.返回值HALT()可能不代表有效参数&&.它可能是void我知道的,这意味着仅仅HALT()不会编译作为参数&&.该(HALT(), 1)结果始终为1,具有类型int,这始终是一个有效的论据&&.因此,无论何种类型,(HALT(), 1)它始终是一个有效的参数.&&HALT()

你最后的评论do{ } while(0)似乎没有多大意义.封闭宏的关键do{ } while(0)是处理外部ifs,而不是if宏定义中的s.你总是需要处理外部if的,因为你的宏总是有可能在外部使用if.在后一个定义do{ } while(0)中不需要,因为宏是一个表达式.作为一个表达,它已经自然地与外部ifs 没有问题.所以,没有必要对它们做任何事情.而且,正如我上面所说的那样,将其封闭do{ } while(0)将完全挫败其目的,将其变为非表达.

  • "在我看来,类似函数的断言宏肯定比非函数式的宏更有用." - 为什么?这个什么时候有用?我认为断言应始终独立于代码中,永远不会在表达式中使用. (3认同)

Gre*_*osz 11

为了完整起见,我在C++中发布了一个drop-in 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;
}
Run Code Online (Sandbox Code Playgroud)

会提示你:

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:
Run Code Online (Sandbox Code Playgroud)

哪里

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

你可以在那里找到更多相关信息:

希望有所帮助.


Chr*_*utz 6

虽然我不确定它为什么(HALT(), 1)而不仅仅是HALT().

我想HALT可能是一个宏(或其他替身名称)exit.假设我们想exit(1)用于我们的HALT命令.exit返回void,不能作为第二个参数进行求值&&.如果你使用逗号运算符来计算它的第一个参数,然后计算并返回它的第二个参数的值,我们有一个整数(1)返回&&,即使我们从未到达那个点因为HALT()会导致我们很久就停止然后.

基本上,任何填充的函数HALT都可能具有返回值void,因为它返回任何值都没有意义.我们可以让它返回一个int,只是为了宏,但如果我们已经用宏来攻击一点,那么hackery就不会受到伤害,是吗?