这是我如何做到这一点的。代码下方讨论。
在一些 .h 文件中:
#define PASS ((void)0)
extern void hook(void);
extern void asserthook(void);
#ifdef DEBUG
#define ENABLE_ASSERTS
#endif // DEBUG
#ifdef ENABLE_ASSERTS
#define AssertMesg(expr, mesg) \
do { \
if (!(expr)) \
{ \
asserthook(); \
fprintf(streamErr, \
"%s(%d): assertion failed: ", __FILE__, __LINE__); \
fprintf(streamErr, "%s\n", mesg); \
fflush(streamErr); \
Exit(10); \
} \
} while (0)
#define Assert(expr) AssertMesg(expr, "")
#else // !ENABLE_ASSERTS
#define AssertMesg(expr, mesg) PASS
#define Assert(expr) PASS
#endif // !ENABLE_ASSERTS
在一些 .c 文件中:
void hook(void)
{
PASS;
}
void asserthook(void)
{
hook();
}
首先,我的断言总是调用asserthook()
which calls hook()
。这些函数只是设置断点的地方;我也有errhook()
一个错误。通常我只是在hook()
自身上设置一个断点,然后每当我的代码被断言删除时,我都会让调试器在错误处停止,堆栈回溯恰好在我需要的位置。
当您尝试创建一个类似于 C 语句的多行宏时,通常的做法是将其放在大括号中,然后将这些大括号括在do
/中while (0)
。那是一个执行一次的循环,所以它真的不是一个循环。但是像这样包装它意味着它是一个语句,当你在行上加上一个分号来终止语句时,它实际上是正确的。因此,像这样的代码将毫无错误地编译并做正确的事情:
if (error)
AssertMesg(0, "we have an error here");
else
printf("We don't have an error after all.\n");
如果你没有做傻do
/while (0)
包装,只是有花括号,上面的代码将无法工作!首先,编译器会想知道为什么你;
在花括号之后和else
;之前有一个 right 其次,将与宏内部else
的隐藏相关联,并且永远不会调用 。您可以使用显式花括号解决后一个问题,但显然最好设置宏以使其适用于所有情况,这就是/为您所做的。if
AssertMesg()
printf()
do
while (0)
(void)0
是我首选的无所事事声明。如果您愿意,您可以使用do {} while (0)
,或者其他任何没有副作用且不使用任何变量的东西。
do
/技巧最糟糕的事情while (0)
是,您有时会看到抱怨do
循环的错误消息,并且由于它隐藏在宏中,您需要记住真正发生的事情。但这是我所知道的使多行宏正常工作的最佳方法。
如果可能,您应该使用静态内联函数而不是宏。但是宏可以完全移植到甚至糟糕的蹩脚的 C 编译器中,并且您需要使用宏来进行断言,以便您可以正确地拥有__FILE__
和__LINE__
扩展。
(您可以编写一个执行实际断言的 varargs 函数,然后只需创建一个扩展为对该函数的单个调用的宏,只要 varargs 在您使用的所有编译器上都能正常工作。我想我现在可以这样做,但我已经有了多行宏解决方案,而且我已经很长时间没有碰过它了。)