问题描述
试图创建一个可用于打印调试消息时,DEBUG定义一个宏,如下面的伪code:
将#define DEBUG 1
#定义debug_print(参数...)如果(调试)fprintf中(标准错误,参数)
这是
如何完成与宏?
如果你使用C99编译器
的#define debug_print(FMT,...)\\
做{如果(调试)fprintf中(标准错误,格式化,__VA_ARGS__); }而(0)
假设你正在使用C99(可变参数列表符号是不是在早期版本支持)。在做{...}而(0)
成语确保code的作用就像一个声明(函数调用)。无条件的使用code可确保编译器总是会检查调试code是有效的—但优化器将删除code当DEBUG为0。
如果你想用的#ifdef DEBUG工作,然后改变测试条件:
#IFDEF DEBUG
#定义DEBUG_TEST 1
#其他
#定义DEBUG_TEST 0
#万一
然后用DEBUG_TEST,我用DEBUG。
如果你坚持一个字符串格式字符串(可能反正是个好主意),你还可以引入的东西像 __ FILE __
, __ LINE__
和 __ __ FUNC
到输出,从而可以提高诊断:
的#define debug_print(FMT,...)\\
做{如果(调试)fprintf中(标准错误,%S:%D:%S():FMT,__FILE__,\\
__LINE__,__func__,__VA_ARGS__); }而(0)
这依赖于字符串连接比程序员编写创建一个更大的格式字符串。
如果你使用C89编译器
如果你被卡住C89和没有有用的编译器扩展,那么就没有一个特别清晰的方式来处理它。我以前用的技术是:
的#define TRACE(X)做{如果(调试)dbg_printf X; }而(0)
然后,在code,写的:
TRACE((信息%d个\\ N,VAR));
双括号是至关重要的—并且为什么你在宏展开有趣的符号。和以前一样,编译器总是检查语法有效性code(这是好的),但只有优化如果调试宏解释为非零调用打印功能。
这确实需要一个支持函数— dbg_printf()中的实施例—处理之类的东西标准错误。它要求你知道如何写可变参数的功能,但是这并不难:
的#include<&STDARG.H GT;
#包括LT&;&stdio.h中GT;无效dbg_printf(为const char * FMT,...)
{
va_list的ARGS;
的va_start(参数,FMT);
vfprintf(标准错误,格式化,参数);
va_end用来(参数);
}
您也可以使用C99这种技术,当然,但 __ VA_ARGS __
技术是整洁的,因为它使用普通函数符号,而不是双括号破解。
为什么至关重要的编译器总能看到调试code?
[老调重弹到另一个答案提出的意见。的]
C99的C89和实现都背后的一个中心思想上面是编译器适当总是能看到调试类printf语句。这对于长期code&MDASH重要; code,将持续十年或二十年。
假设一块$ C $的c已大多休眠(稳定)为了数年,但现在需要改变。您重新启用调试跟踪 - 但它是令人沮丧不得不调试调试(跟踪)code,因为它是指在多年的稳定维持已重命名或重新输入,变量。如果编译器(后pre-处理器)的总是能看到打印语句,它确保了周围的任何变化都没有失效诊断。如果编译器不看到打印语句,它不能保护你对自己的粗心(或您的同事或合作者的疏忽)。参见的实践由Kernighan和派克,尤其是第8章(见维基百科)。
这是在那里,这样做的经验和MDASH;我基本上是用来在其他的答案中描述的技术,其中非调试版本并没有看到类似printf语句数年(十年以上)。但是,我在对面的TPOP来到意见(见我的previous评论),然后做了使若干年后一些调试code,跑进打破了调试变化的情况下的问题。有好几次,总是有验证印刷已经从后面的问题救了我。
我使用NDEBUG只控制的断言,和一个单独的宏(通常DEBUG)来控制调试跟踪是否被内置到该程序。即使在调试跟踪是内置的,我经常不想调试输出无条件地出现,所以我有机制来控制是否显示输出(调试水平,而不是调用fprintf中()直接,我称之为调试打印函数只有有条件地打印所以code的相同版本可以打印或不打印基于程序选项)。我也有code更大节目的多子系统的版本,这样我就可以有计划生产不同量的微量的不同部分 - 运行控制下
我主张所有构建,编译器应该看到诊断报告;然而,除非调试启用编译器不会产生任何code的调试跟踪语句。基本上,这意味着你的所有code是由编译器每次编译时检查 - 无论是为了释放或调试。这是个好东西!
debug.h - 1.2版(1990年5月1日)
/ *
@(#)文件:$ RCSfile:debug.h,V $
@(#)版本:$修订:$ 1.2
@(#)上次修改:$日期:1990年5月1日12时55分39秒$
@(#)用途:定义为系统调试
@(#)作者:J-莱弗勒
* /的#ifndef DEBUG_H
#定义DEBUG_H/ * - 宏定义* /#IFDEF DEBUG
将#define TRACE(X)db_print点¯x
#其他
将#define TRACE(X)
#ENDIF / * * DEBUG // * - 声明* /#IFDEF DEBUG
EXTERN INT调试;
#万一#ENDIF / * * DEBUG_H /
debug.h - 3.6版(2008-02-11)
/ *
@(#)文件:$ RCSfile:debug.h,V $
@(#)版本:$修订:$ 3.6
@(#)上次修改:$日期:2008年2月11日6时46分37秒$
@(#)用途:定义为系统调试
@(#)作者:J-莱弗勒
@(#)版权所有:(C)JLSS 1990-93,1997-99,2003,2005,2008
@(#)产品:产品:
* /的#ifndef DEBUG_H
#定义DEBUG_H下列块#ifdef HAVE_CONFIG_H
的#includeconfig.h中
#ENDIF / * * HAVE_CONFIG_H // *
**用法:TRACE((水平,FMT,...))
**级别是必须是可操作用于输出的调试级别
** 出现。 FMT是一个格式化字符串。 ...是什么额外的
**参数FMT要求(可能没有)。
**非调试宏意味着code被验证,但从来没有被调用。
** - 的编程实践见第8章,由Kernighan和派克。
* /
#IFDEF DEBUG
将#define TRACE(X)db_print点¯x
#其他
将#define TRACE(X)做{如果(0)db_print X; }而(0)
#ENDIF / * * DEBUG /皮棉的#ifndef
#IFDEF DEBUG
/ *此字符串无法进行的extern - 一般*多重定义/
静态为const char jlss_id_debug_enabled [] =@(#)*** *** DEBUG
#ENDIF / * * DEBUG /
#IFDEF MAIN_PROGRAM
为const char jlss_id_debug_h [] =@(#)$编号:debug.h,V 3.6 2008年2月11日6时46分37秒jleffler精通$;
#ENDIF / * * MAIN_PROGRAM /
#ENDIF / *皮棉* /#包括LT&;&stdio.h中GT;EXTERN INT db_getdebug(无效);
EXTERN INT db_newindent(无效);
EXTERN INT db_oldindent(无效);
EXTERN INT db_setdebug(INT级);
EXTERN INT db_setindent(int i)以;
EXTERN无效db_print(INT级,为const char * FMT,...);
EXTERN无效db_setfilename(为const char * FN);
EXTERN无效db_setfileptr(FILE * FP);
的extern FILE * db_getfileptr(无效);/ *半私有函数* /
EXTERN为const char * db_indent(无效);/ *********** \\
**多种调试SUBSYSTEMS code **
\\ *********** // *
**用法:MDTRACE((SUBSYS,水平,FMT,...))
**SUBSYS就是这本声明所属的调试系统。
**子系统的意义是由程序员确定
**除了诸如功能db_print参阅子系统0。
**级别是必须运行的调试级别
**输出出现。 FMT是一个格式化字符串。 ...是
**任何额外的参数FMT要求(可能没有)。
**非调试宏意味着code被验证,但从来没有被调用。
* /
#IFDEF DEBUG
#定义MDTRACE(X)db_mdprint点¯x
#其他
#定义MDTRACE(X)做{如果(0)db_mdprint X; }而(0)
#ENDIF / * * DEBUG /EXTERN INT db_mdgetdebug(INT SUBSYS);
EXTERN INT db_mdparsearg(字符* ARG);
EXTERN INT db_mdsetdebug(INT SUBSYS,INT级);
EXTERN无效db_mdprint(INT SUBSYS,INT级,为const char * FMT,...);
EXTERN无效db_mdsubsysnames(字符常量* const的*名称);#ENDIF / * * DEBUG_H /
单参数C99变种
凯尔·勃兰特问:
There's one simple, old-fashioned hack:
debug_print("%s\n", "Foo");
The GCC-only solution also provides support for that.
However, you can do it with the straight C99 system by using:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
Compared to the first version, you lose the limited checking that requires the 'fmt' argument, which means that someone could call 'debug_print()' with no arguments. Whether the loss of checking is a problem at all is debatable.
GCC-specific Technique
Some compilers may offer extensions for other ways of handling variable-length argument lists in macros. Specifically, as first noted in the comments by Hugo Ideler, GCC allows you to omit the comma that would normally appear after the last 'fixed' argument to the macro. It also allows you to use ##__VA_ARGS__
in the macro replacement text, which deletes the comma preceding the notation if, but only if, the previous token is a comma:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, ##__VA_ARGS__); } while (0)
This solution retains the benefits of first version.
Why the do-while loop?
You want to be able to use the macro so it looks like a function call, which means it will be followed by a semi-colon. Therefore, you have to package the macro body to suit. If you use an if
statement without the surrounding do { ... } while (0)
, you will have:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Now, suppose you write:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
Unfortunately, that indentation doesn't reflect the actual control of flow, because the preprocessor produces code equivalent to this (indented and braces added to emphasize the actual meaning):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
The next attempt at the macro might be:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
And the same code fragment now produces:
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);
And the else
is now a syntax error. The do { ... } while(0)
loop avoids both these problems.
There's one other way of writing the macro which might work:
/* 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.
这篇关于ç的#define宏调试印刷的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!