我偶然发现了一篇内容丰富的文章: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。
您怎么看?
答案 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
行为和可用性,那么在{{的定义中使用if
或do { } 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:
哪里
abort()
(在Windows上,
系统将提示用户附加调试器)abort()
你可以在那里找到更多相关信息:
希望有所帮助。
答案 2 :(得分:6)
虽然我不确定原因是
(HALT(), 1)
而不只是HALT()
。
我认为HALT
可能是exit
的宏(或其他替代名称)。假设我们想要exit(1)
用于HALT
命令。 exit
返回void
,无法将其评估为&&
的第二个参数。如果你使用逗号运算符来计算它的第一个参数,然后计算并返回它的第二个参数的值,我们有一个整数(1)返回&&
,即使我们从未到达那个点,因为{{ 1}}会让我们在此之前很久就停止。
基本上,任何填充HALT()
的函数可能会返回HALT
,因为它返回任何值都没有意义。我们可以让它返回一个void
,只是为了宏,但是如果我们已经习惯了一个宏,那么更多的hackery不会受到伤害,是吗?