2011-03-09 132 views
25

我偶然发现了一篇内容丰富的文章:http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ 其中指出了我当前调试宏套件中存在的大量问题。自定义C++声明宏

如果您关注该链接,则会在文章结尾处给出宏的最终版本的完整代码。

所呈现的一般形式是这样的(有人请纠正我,如果我在调换它是错的):

#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。

您认为如何?

+1

+1。有趣的链接。 – Maxpm 2011-03-09 21:35:52

+0

另请参见我的答案如下:http://stackoverflow.com/a/21827201/216063 – 2014-04-26 12:41:24

回答

26

在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行为和可用性,然后在ASSERT定义使用ifdo { } while (0)是毫无疑问的。其中一种仅限于表达式,即使用?:运算符或短路逻辑运算符。

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

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

您对do{ } while(0)的最新评论似乎没有多大意义。将宏包含进do{ } while(0)的要点是在宏定义内处理外部if,而不是处理if。您总是必须处理外部if s,因为您的宏总是有机会在外部使用if。在后面的定义中不需要do{ } while(0),因为该宏是表达式。作为一个表达,它已经自然地与外部的if s没有问题。所以,没有必要对他们做任何事情。而且,正如我上面所说的那样,将它封入do{ } while(0)将彻底击败它的目的,将其变为非表达。

+2

“在我看来,类似功能的断言宏比非功能类更有用。” - 为什么?这一切何时有用?我认为断言应该始终独立于代码中,永远不会用在表达式中。 – 2011-03-09 22:02:37

+0

我会说这是优越的,因为assert可以在表达式中使用_able_以及在典型的“独立”方式中使用。更多的用户选择。 @AndreyT,谢谢你让我清楚地看到这种区别。它看起来很酷,但现在看起来'do {} while(0)'结构并不是非常有用。事实是,我一直在使用相当多的宏,这些宏只包含一个大的if语句,在这一点上显然不是正确的做法。 – 2011-03-09 22:14:45

6

虽然我不知道为什么它是(HALT(), 1),而不是仅仅HALT()

我想HALT可能是一个宏(或其他替代名称)为exit。假设我们想要使用exit(1)作为我们的HALT命令。 exit返回void,它不能作为&&的第二个参数进行评估。如果您使用逗号运算符来评估它的第一个参数,然后评估并返回它的第二个参数的值,我们有一个整数(1)返回到&&,即使我们从未达到那个点,因为HALT()会导致我们停止早在那之前。

基本上,任何填写HALT的函数都可能返回值为void,因为它返回任何值都没有意义。我们可能使它返回一个int,只是为了宏观,但如果我们已经用宏观黑客多一点hackery不能伤害,可以吗?

+1

所以这是一种让编译器很开心的方式,并且通过给它一个返回的值来调用exit作为副作用(终止程序!相当一个我必须说的副作用)。整齐。 – 2011-03-09 21:50:51

8

为了完整起见,我发表了一个活动的2个文件断言宏实现在C++:

#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()立即

你可以找到更多关于它的存在:

希望有所帮助。

+0

谢谢,这是非常整洁。现在我会坚持使用自己的解决方案,但我会说,与你在这里相比,它看起来像是一个丑陋的黑客。 – 2014-02-17 11:04:13

+0

如果您决定切换,请随时与我联系。我到目前为止在Mac,Linux,Windows,iOS和Android上测试过它。 – 2014-02-17 14:16:31