试图创建一个宏,可以用于在定义debug时打印调试消息,就像下面的伪代码:

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

这是如何用宏来完成的呢?


当前回答

根据http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html, __VA_ARGS__之前应该有##。

否则,宏#define dbg_print(format,…)printf(format, __VA_ARGS__)将不会编译以下示例:dbg_print("hello world");

其他回答

#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)

所以,当使用gcc时,我喜欢:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

因为它可以插入到代码中。

假设您正在尝试调试

printf("%i\n", (1*2*3*4*5*6));

720

然后你可以把它改成:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

你可以得到一个表达式被求值为什么的分析。

这样就避免了双重评估的问题,但缺少物理系统确实会导致名称冲突。

然而它是嵌套的:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

所以我认为,只要避免使用g2rE3作为变量名,就可以了。

当然,我发现它(以及字符串的联合版本,以及调试级别的版本等)非常宝贵。

如果使用C99或更高版本的编译器

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

它假设您使用的是C99(在早期版本中不支持变量参数列表表示法)。do{…} while(0)习惯用法确保代码的行为像语句(函数调用)。代码的无条件使用确保编译器总是检查您的调试代码是否有效——但是当debug为0时,优化器将删除代码。

如果你想使用#ifdef DEBUG,那么改变测试条件:

#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif

然后在我使用DEBUG的地方使用DEBUG_TEST。

如果你坚持使用字符串字面值作为格式字符串(可能是个好主意),你也可以在输出中引入__FILE__、__LINE__和__func__这样的东西,这可以改善诊断:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

这依赖于字符串连接来创建一个比程序员编写的更大的格式字符串。

如果你使用C89编译器

如果你被C89所困,没有有用的编译器扩展,那么就没有一个特别干净的方法来处理它。我曾经使用的技巧是:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

然后,在代码中,写:

TRACE(("message %d\n", var));

双括号是至关重要的-这就是为什么在宏观展开中有有趣的符号。像以前一样,编译器总是检查代码的语法有效性(这很好),但优化器只在DEBUG宏的计算结果为非零时调用打印函数。

这确实需要一个支持函数——示例中的dbg_printf()——来处理像'stderr'这样的事情。它要求你知道如何编写变量函数,但这并不难:

#include <stdarg.h>
#include <stdio.h>

void dbg_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

当然,你也可以在C99中使用这种技术,但__VA_ARGS__技术更简洁,因为它使用常规函数符号,而不是双括号。

为什么编译器总是看到调试代码是至关重要的?

[重复对另一个答案的评论。]

上面的C99和C89实现背后的一个核心思想是,编译器本身总是能看到类似printf的调试语句。这对于长期代码(持续十年或二十年的代码)很重要。

Suppose a piece of code has been mostly dormant (stable) for a number of years, but now needs to be changed. You re-enable debugging trace - but it is frustrating to have to debug the debugging (tracing) code because it refers to variables that have been renamed or retyped, during the years of stable maintenance. If the compiler (post pre-processor) always sees the print statement, it ensures that any surrounding changes have not invalidated the diagnostics. If the compiler does not see the print statement, it cannot protect you against your own carelessness (or the carelessness of your colleagues or collaborators). See 'The Practice of Programming' by Kernighan and Pike, especially Chapter 8 (see also Wikipedia on TPOP).

这是“曾经有过,做过”的经验——我基本上使用了其他答案中描述的技术,在非调试构建中,多年(超过十年)都看不到类似printf的语句。但是我在TPOP中遇到了这个建议(请参阅我之前的评论),然后在几年之后启用了一些调试代码,并遇到了更改上下文破坏调试的问题。有几次,总是对打印进行验证使我避免了以后的问题。

I use NDEBUG to control assertions only, and a separate macro (usually DEBUG) to control whether debug tracing is built into the program. Even when the debug tracing is built in, I frequently do not want debug output to appear unconditionally, so I have mechanism to control whether the output appears (debug levels, and instead of calling fprintf() directly, I call a debug print function that only conditionally prints so the same build of the code can print or not print based on program options). I also have a 'multiple-subsystem' version of the code for bigger programs, so that I can have different sections of the program producing different amounts of trace - under runtime control.

我主张对于所有构建,编译器都应该看到诊断语句;但是,除非启用调试,否则编译器不会为调试跟踪语句生成任何代码。基本上,这意味着每次编译时编译器都会检查所有代码——无论是发布还是调试。这是一件好事!

Debug.h - version 1.2 (1990-05-01)

/*
@(#)File:            $RCSfile: debug.h,v $
@(#)Version:         $Revision: 1.2 $
@(#)Last changed:    $Date: 1990/05/01 12:55:39 $
@(#)Purpose:         Definitions for the debugging system
@(#)Author:          J Leffler
*/

#ifndef DEBUG_H
#define DEBUG_H

/* -- Macro Definitions */

#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)
#endif /* DEBUG */

/* -- Declarations */

#ifdef DEBUG
extern  int     debug;
#endif

#endif  /* DEBUG_H */

Debug.h - version 3.6 (2008-02-11)

/*
@(#)File:           $RCSfile: debug.h,v $
@(#)Version:        $Revision: 3.6 $
@(#)Last changed:   $Date: 2008/02/11 06:46:37 $
@(#)Purpose:        Definitions for the debugging system
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product:        :PRODUCT:
*/

#ifndef DEBUG_H
#define DEBUG_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);

#endif /* DEBUG_H */

C99或更高版本的单参数变体

凯尔·勃兰特问道:

不管怎样,这样做即使没有参数,debug_print仍然有效?例如: debug_print (" Foo ");

有一个简单而老式的方法:

debug_print("%s\n", "Foo");

下面显示的只支持gcc的解决方案也提供了支持。

然而,你可以使用直接的C99系统,通过使用:

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

与第一个版本相比,您失去了需要'fmt'参数的有限检查,这意味着有人可以尝试在不带参数的情况下调用'debug_print()'(但fprintf()参数列表中的末尾逗号将无法编译)。检查的缺失到底是不是一个问题还有待商榷。

针对单个参数的gcc特定技术

一些编译器可能提供了在宏中处理变长参数列表的其他方法的扩展。具体来说,正如Hugo Ideler在评论中首先指出的那样,GCC允许您省略通常出现在宏的最后一个“固定”参数之后的逗号。它还允许你在宏替换文本中使用##__VA_ARGS__,当但仅当前一个标记是逗号时,它会删除符号前面的逗号:

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)

这个解决方案保留了需要format参数的好处,同时接受format后面的可选参数。

Clang还支持这种技术以实现GCC兼容性。


为什么是do-while循环?

你来这里的目的是什么?

您希望能够使用宏,使它看起来像一个函数调用,这意味着它将后面跟着一个分号。因此,必须对宏主体进行适当的包装。如果你使用If语句而不包含do{…} while(0),你将得到:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) fprintf(stderr, __VA_ARGS__)

现在,假设你这样写:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

不幸的是,这种缩进并不能反映对流的实际控制,因为预处理器生成了与此等效的代码(缩进和括号的添加是为了强调实际意义):

if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

对宏的下一次尝试可能是:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

相同的代码片段现在生成:

if (x > y)
    if (DEBUG)
    {
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    }
; // Null statement from semi-colon after macro
else
    do_something_useful(x, y);

else现在是一个语法错误。do{…} while(0)循环避免了这两个问题。

还有一种编写宏的方法:

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

This leaves the program fragment shown as valid. The (void) cast prevents it being used in contexts where a value is required — but it could be used as the left operand of a comma operator where the do { ... } while (0) version cannot. If you think you should be able to embed debug code into such expressions, you might prefer this. If you prefer to require the debug print to act as a full statement, then the do { ... } while (0) version is better. Note that if the body of the macro involved any semi-colons (roughly speaking), then you can only use the do { ... } while(0) notation. It always works; the expression statement mechanism can be more difficult to apply. You might also get warnings from the compiler with the expression form that you'd prefer to avoid; it will depend on the compiler and the flags you use.


TPOP之前在http://plan9.bell-labs.com/cm/cs/tpop和http://cm.bell-labs.com/cm/cs/tpop,但现在都破了(2015-08-10)。


GitHub中的代码

如果你感到好奇,你可以在我的SOQ (Stack)中查看GitHub中的这段代码 数据库中的debug.c、debug.h和mddebug.c文件 src / libsoq 子目录。

这是我所使用的:

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

即使没有额外的参数,它也具有正确处理printf的好处。在DBG ==0的情况下,即使是最笨的编译器也没有什么可考虑的,因此不会生成任何代码。

#define PRINT_LOG(str_format, ...) { \
    time_t curtime=time (NULL); \
    struct tm *ltm = localtime (&curtime); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] " str_format, \
        ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday, \
        ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ##__VA_ARGS__); \
}
    
PRINT_LOG("[%d] Serving client, str=%s, number=%d\n", getpid(), "my str", 10);