尝试创建一个宏,当 DEBUG 被定义时可以用于打印调试信息,如下伪代码所示:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
如何使用宏来实现这个?
尝试创建一个宏,当 DEBUG 被定义时可以用于打印调试信息,如下伪代码所示:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
如何使用宏来实现这个?
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
#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中,没有有用的编译器扩展,那么处理它的方法并不特别干净。我曾经使用的技术是:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
然后,在代码中编写:
TRACE(("message %d\n", var));
#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);
}
/*
@(#)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 */
/*
@(#)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 */
Kyle Brandt问道:
Anyway to do this so
debug_print
still works even if there are no arguments? For example:debug_print("Foo");
有一个简单而古老的技巧:
debug_print("%s\n", "Foo");
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
fprintf()
的参数列表中的尾随逗号将无法编译)。 是否丧失检查是一个问题值得商榷。##__VA_ARGS__
,如果前一个标记是逗号,则删除其前面的逗号。#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
这种解决方案保留了需要格式参数的好处,同时接受格式后的可选参数。
这种技术也被 Clang 支持,以实现与 GCC 的兼容性。
这里为什么要使用
do while
循环?
您希望能够使用宏来模拟函数调用,这意味着它将跟随一个分号。因此,您必须对宏体进行打包以适应。如果您在没有周围的do {...} while (0)
的情况下使用if
语句,则会有:
/* 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))
(void)
强制转换可以防止在需要值的情况下使用它,但它可以用作逗号运算符的左操作数,而do { ... } while(0)
版本则不能。 如果您认为应该能够将调试代码嵌入此类表达式中,则可能更喜欢这种方法。 如果您希望要求调试打印作为完整语句执行,则do { ... } while(0)
版本更好。 请注意,如果宏的主体涉及任何分号(粗略地说),则只能使用do { ... } while(0)
表示法。 它总是有效的; 表达式语句机制可能更难应用。 您还可能会收到编译器的警告,而您希望避免使用表达式形式; 这将取决于您使用的编译器和标志。
TPOP曾经在http://plan9.bell-labs.com/cm/cs/tpop和http://cm.bell-labs.com/cm/cs/tpop上,但现在(2015-08-10)两者都已失效。
如果你感兴趣,可以在我的SOQ(Stack Overflow问题)存储库中查看这些代码,文件位于src/libsoq子目录下的debug.c
、debug.h
和mddebug.c
。
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
- Kevin__FILE__,__LINE__,__func__,__VA_ARGS__
的示例中,如果您没有printf参数,即如果您只调用debug_print(“Some msg\n”);
,它将无法编译。通过使用fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
可以解决这个问题。##__VA_ARGS__
允许将无参数传递给函数。 - mc_electron#define debug_print(fmt, ...)
和#define debug_print(...)
之间的区别在于,前者至少需要一个参数,即格式字符串(fmt
)和零个或多个其他参数;后者总共需要零个或多个参数。如果你使用第一个带有debug_print()
的宏定义,预处理器会报错,指出宏定义的错误使用方式,而第二个则不会。然而,由于替换文本不是有效的C语言,仍然会导致编译错误。因此,实际上并没有太大的区别,这就是为什么使用术语“有限检查”的原因。 - Jonathan Lefflerflockfile()
? - HeinrichStack我使用类似这样的代码:
#ifdef DEBUG
#define D if(1)
#else
#define D if(0)
#endif
那么我只需要在前面加上D:
D printf("x=%0.3f\n",x);
编译器会看到调试代码,不存在逗号问题,并且它能够在任何地方运行。此外,当printf
不足以使用时,例如你必须转储一个数组或计算程序本身多余的一些诊断值时,它也可以使用。
编辑:好的,如果有else
,并且被这个插入的if
截取了,这可能会产生问题。以下是一个解决方法:
#ifdef DEBUG
#define D
#else
#define D for(;0;)
#endif
for(;0;)
,当你写类似于D continue;
或者D break;
这样的语句时可能会产生问题。 - ACcreator#define PRINTF if (0) printf
,因为这样你就不需要在每个地方都加前缀了。 - Ed Graham对于一个可移植的(ISO C90)实现,你可以使用双括号,像这样;
#include <stdio.h>
#include <stdarg.h>
#ifndef NDEBUG
# define debug_print(msg) stderr_printf msg
#else
# define debug_print(msg) (void)0
#endif
void
stderr_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
int
main(int argc, char *argv[])
{
debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
return 0;
}
或者(拙劣的做法,不建议使用)
#include <stdio.h>
#define _ ,
#ifndef NDEBUG
# define debug_print(msg) fprintf(stderr, msg)
#else
# define debug_print(msg) (void)0
#endif
int
main(int argc, char *argv[])
{
debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
return 0;
}
以下是我使用的版本:
#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
__func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif
#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif
assert()
函数也是这样工作的,我通常会重用NDEBUG
宏来进行自己的调试代码... - Christoph-DNDEBUG
的原因。 - Christoph__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)
#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
你可以获得表达式被评估为什么的分析。
它受到双重评估问题的保护,但缺少gensym会导致名称冲突。
然而,它确实可以嵌套:
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作为变量名,你就不会有问题。
当然,我发现它(以及针对字符串的类似版本、调试级别等的版本)非常有用。
这是我使用的:
#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/
#endif
它有一个很好的好处,可以正确处理printf,即使没有其他参数。如果DBG == 0,甚至最愚蠢的编译器也得不到任何可以利用的东西,因此不会生成任何代码。
我最喜欢下面的var_dump
,当它被调用为:
var_dump("%d", count);
输出如下:
patch.c:150:main(): count = 0
感谢 @"Jonathan Leffler"。所有代码都符合C89标准:
代码
#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
__FILE__, __LINE__, __func__); debug_vprintf x; }} while (0)
/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
__FILE__, __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)
#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
__FILE__, __LINE__, __func__); }} while (0)