自定义C ++断言宏

时间:2011-03-09 21:26:25

标签: c++ macros assert

我偶然发现了一篇内容丰富的文章:http://cnicholson.net/2009/02/stupid-c-tricks-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) 

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

一个是你不能将这个宏用于三元运算符(即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

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

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

您怎么看?

3 个答案:

答案 0 :(得分:28)

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

int sum = (assert(a > 0), a) + (assert(b < 0), b);

在功能上与

相同
assert(a > 0 && b < 0)
int sum = a + b;

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

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

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

至于if ......这样做是因为(HALT(), 1)运算符需要一个有效的参数。 &&的返回值可能不代表HALT()的有效参数。对于我所知道的,它可能是&&,这意味着仅仅void不会编译为HALT()的参数。 &&始终评估为(HALT(), 1)并且类型为1,这始终是int的有效参数。因此,无论&&的类型如何,(HALT(), 1)始终是&&的有效参数。

您对HALT()的最后评论似乎没有多大意义。将宏括入do{ } while(0)的要点是处理外部do{ } while(0) s,而不是宏定义中的if s。您总是必须处理外部if,因为您的宏总是有可能在外部if中使用。在后一种定义中,不需要if,因为该宏是表达式。作为一个表达,它已经自然地与外部do{ } while(0)没有问题。所以,没有必要对它们做任何事情。此外,正如我上面所说,将其封入if将完全打败其目的,将其变为非表达。

答案 1 :(得分: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;
}

将提示您:

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)orever:记住断言触发的文件和行 忽略它以继续执行程序
  • 忽略(A)ll:忽略所有剩余的断言(所有文件和行)
  • (D)ebug:如果连接则进入调试器,否则abort()(在Windows上, 系统将提示用户附加调试器)
  • A(b)ort:立即致电abort()

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

希望有所帮助。

答案 2 :(得分:6)

  

虽然我不确定原因是(HALT(), 1)而不只是HALT()

我认为HALT可能是exit的宏(或其他替代名称)。假设我们想要exit(1)用于HALT命令。 exit返回void,无法将其评估为&&的第二个参数。如果你使用逗号运算符来计算它的第一个参数,然后计算并返回它的第二个参数的值,我们有一个整数(1)返回&&,即使我们从未到达那个点,因为{{ 1}}会让我们在此之前很久就停止。

基本上,任何填充HALT()的函数可能会返回HALT,因为它返回任何值都没有意义。我们可以让它返回一个void,只是为了宏,但是如果我们已经习惯了一个宏,那么更多的hackery不会受到伤害,是吗?