2

我的问题可能与我们何时应该在 C 中使用断言有关?但是我仍然想知道当我有一个项目在一些地方散布着一堆断言时,哪个会是更好的做法:

我是否使用 DNDEBUG 标志将断言转换为无操作(如相关问题中所建议的那样),还是使用 if 宏包围所有断言

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

....

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

....

并在编译时使用 -D TEST 选项?是否有某种标准或“惯例”?我觉得后者会更整洁..谢谢!

4

2 回答 2

3

是否有某种标准或“惯例”?

是:NDEBUG在生产代码中定义。这是执行此操作的 ISO C 标准方式,并且只会为您的编译周期增加几毫秒(或与未定义相比节省一些毫秒NDEBUG,因为无论如何您都包含它),因为编译器/预处理器必须扫描<assert.h>以找出它有从您的代码中删除内容。它不会使您的可执行文件膨胀或添加任何其他类型的运行时开销,除非您在一个#ifndef NDEBUG块中做疯狂的事情。

于 2012-11-16T22:44:59.053 回答
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()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的隐藏相关联,并且永远不会调用 。您可以使用显式花括号解决后一个问题,但显然最好设置宏以使其适用于所有情况,这就是/为您所做的。ifAssertMesg()printf()dowhile (0)

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

do/技巧最糟糕的事情while (0)是,您有时会看到抱怨do循环的错误消息,并且由于它隐藏在宏中,您需要记住真正发生的事情。但这是我所知道的使多行宏正常工作的最佳方法。

如果可能,您应该使用静态内联函数而不是宏。但是宏可以完全移植到甚至糟糕的蹩脚的 C 编译器中,并且您需要使用宏来进行断言,以便您可以正确地拥有__FILE____LINE__扩展。

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

于 2012-11-16T23:14:01.417 回答