在我们的C代码库中,我们有断言宏,如:
ASSERT3(x, ==, y) // x=0, y=1 results in "main.c:45: 'x == y' (0 == 1) is untrue"
ASSERT(x == y) // x=0, y=1 results in "main.c:45: 'x == y' is untrue"
显然,当您在失败后试图调试某些东西时,ASSERT3
表单更有帮助,因为它告诉您变量的值是什么。
然而,当您需要执行更复杂的断言时(特别是那些包含||
的断言,因为您可以将具有&&
的断言拆分为多个断言),例如ASSERT(x == y || y != 0 || x == 2)
,您就不能再利用令人惊叹的ASSERT3
格式了。显然,我可以构建一个像ASSERT11(x, ==, y, ||, y, !=, 0, ||, x, ==, 2)
这样的宏,但理想情况下,我想创建一个可以处理可变数量的参数的宏,并找出自己要打印的内容。要做到这一点,我认为我需要宏过滤掉只是逻辑运算符的参数,这样它就不会试图打印它们的值——有什么方法可以做到这一点吗?
我曾经有过这种感觉,也有过这样的想法,但事实证明断言是用于不应该发生的事情的,因此为它们提供花哨的输出实际上没有意义。您要寻找的是在不满足这些条件时打印的调试消息,因此您应该这样编码:
if (!condition) {
DEBUG(whatever); /* or however your ASSERT() macro prints */
PANIC(); /* or however your ASSERT() macro aborts */
}
在这种情况下,我实际上认为代码更容易编写,更容易阅读,并且明显比您最终将使用的复杂的ASSERT语句更可靠。更不用说,一旦断言变得如此复杂,肯定还有其他重要的相关信息实际上不在assert语句中。
这可能不值得您投入精力,但我会给您一个大致的轮廓,您可以决定。
理论上可以用可变宏
来完成#define UBER_ASSERT(...) uberAssert( __FILE__, __LINE__, __VA_ARGS__ )
和可变函数
void uberAssert( char *filename, int linenumber, char *format, ... )
典型用法如下
UBER_ASSERT( "%1d == %2d || %2d != 0 || %1d == 2", x, y );
和一个最小的例子看起来像这样
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#define UBER_ASSERT(...) uberAssert( __FILE__, __LINE__, __VA_ARGS__ )
int evaluateExpression( char *format, va_list args )
{
int x = va_arg( args, int );
int y = va_arg( args, int );
printf( "evaluating "%s" with x=%d y=%dn", format, x, y );
return 0;
}
void displayExpression( char *format, va_list args )
{
int x = va_arg( args, int );
int y = va_arg( args, int );
printf( "%s with x=%d y=%dn", format, x, y );
}
void uberAssert( char *filename, int linenumber, char *format, ... )
{
va_list args, args2;
va_start( args, format );
va_copy( args2, args );
if ( !evaluateExpression( format, args ) )
{
printf( "%s:%d: ", filename, linenumber );
displayExpression( format, args2 );
abort();
}
va_end( args );
va_end( args2 );
}
int main( void )
{
int x = 3;
int y = 5;
UBER_ASSERT( "%1d==%2d || %2d!=0 || %1d==2", x, y );
}
困难的部分是编写表达式求值器和表达式显示函数,我把它留给读者作为练习。