C - 如何使用宏在生产版本中省略assert()

时间:2012-11-16 22:38:14

标签: c

我的问题可能与When should we use asserts in C?部分相关但我仍然想知道当我的项目中有一堆断言散布在各个地方时哪种方法会更好:

我是否使用DNDEBUG标志将断言转换为无操作(如相关问题中所述),或者使用if宏包围所有断言

#ifdef TEST
#include <assert.h>
#endif

....

#ifdef TEST
    assert(...);
#endif

....

并在补充时使用-D TEST选项?这有什么标准或“惯例”吗?我觉得后者会更整洁.. 谢谢!

2 个答案:

答案 0 :(得分:3)

  

对此有什么标准或“惯例”吗?

是:在生产代码中定义NDEBUG。它是ISO C标准的这种方式,并且只为你的编译周期增加了几毫秒(或者保存了一些与未定义NDEBUG相比,因为你还包括它)因为编译器/预处理器必须扫描<assert.h>要发现必须从代码中删除内容。它不会破坏你的可执行文件或添加任何其他类型的运行时开销,除非你在#ifndef NDEBUG块中做了疯狂的事情。

答案 1 :(得分:0)

我是这样做的。在代码下面讨论。

在某些.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()来调用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;第二,else会与if宏中的隐藏AssertMesg()相关联,而printf()永远不会被调用。您可以使用显式花括号修复后一个问题,但显然更好的是设置宏以便它适用于所有情况,这就是do / while (0)为您所做的事情。

(void)0是我首选的无所作为的声明。如果您愿意,可以使用do {} while (0),或者其他任何没有副作用且不使用任何变量的内容。

关于do / while (0)技巧的最糟糕的事情是,您有时会看到抱怨do循环的错误消息,并且因为它隐藏在宏内部,您需要记住什么真的很棒。但这是我知道使多线宏正常工作的最好方法。

如果可能,您应该使用静态内联函数而不是宏。但宏可以完全移植到甚至糟糕的C语言编译器中,并且你需要使用一个宏作为断言,这样你就可以正确扩展__FILE____LINE__

(您可以编写一个执行实际断言的varargs函数,然后只需将一个宏扩展为对该函数的单个调用,只要varargs在您使用的所有编译器上正常工作。我想我能做到现在,但我已经有多线宏解决方案工作,我很长时间没有触及它。)