#define宏用于C中的调试打印?

时间:2009-10-29 16:17:32

标签: c c-preprocessor

尝试创建一个可在定义DEBUG时用于打印调试消息的宏,如下面的伪代码:

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

如何用宏完成?

14 个答案:

答案 0 :(得分:382)

答案 1 :(得分:28)

我使用这样的东西:

#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

答案 2 :(得分:11)

对于便携式(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;
}

或(hackish,不推荐它)

#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;
}

答案 3 :(得分:10)

这是我使用的版本:

#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

答案 4 :(得分:9)

我会做像

这样的事情
#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

我认为这更清洁。

答案 5 :(得分:7)

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

答案 6 :(得分:6)

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

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

答案 7 :(得分:1)

这就是我使用的:

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

即使没有额外的参数,它也能很好地处理printf。如果DBG == 0,即使最笨的编译器也没有任何东西可以咀嚼,因此不会生成任何代码。

答案 8 :(得分:1)

我多年来一直在研究如何做到这一点,最后提出了解决方案。但是,我还不知道这里还有其他解决方案。首先,与Leffler's answer不同,我没有看到他的论点,即应该始终编译调试打印。我不想在我的项目中执行大量不需要的代码,如果不需要的话,我需要进行测试并且可能没有进行优化。

每次不编译可能听起来比在实际操作中更糟糕。您最终会得到有时无法编译的调试打印,但在最终确定项目之前编译和测试它们并不困难。使用此系统,如果您使用三个级别的调试,只需将其置于调试消息级别3,修复编译错误并在最终确定您的代码之前检查其他任何错误。 (当然,编译调试语句并不能保证它们仍然按预期工作。)

我的解决方案也提供了调试细节级别;如果你把它设置到最高级别,它们都会编译。如果您最近一直在使用高调试细节级别,那么他们当时都能够编译。最后的更新应该很简单。我从来没有需要超过三个级别,但Jonathan说他使用了九个级别。这种方法(如Leffler's)可以扩展到任意数量的级别。我的方法的用法可能更简单;在代码中使用时只需要两个语句。然而,我也在编写CLOSE宏 - 虽然它没有做任何事情。如果我发送到文件,可能会这样。

反对成本,测试它们以确定它们将在交付之前编译的额外步骤是

  1. 你必须相信它们才能得到优化,如果你有足够的优化水平,那么应该会发生这种情况。
  2. 此外,如果您为了测试目的关闭优化而进行发布编译,他们可能会胜出(这是罕见的);并且他们几乎肯定在调试期间根本不会 - 因此执行数十或数百个&#34; if(DEBUG)&#34;运行时的语句;从而减慢执行速度(这是我的主要反对意见),不太重要的是,增加可执行文件或DLL的大小;因此执行和编译时间。然而,Jonathan告诉我他的方法也可以根本不编译语句。
  3. 在现代预取处理器中,分支实际上相对昂贵。如果您的应用不是时间关键的应用,也许不是什么大问题;但是,如果性能是一个问题,那么,是的,这是一个足够大的交易,我更愿意选择执行速度更快的调试代码(并且在极少数情况下可能会更快发布,如上所述)。

    所以,我想要的是一个调试打印宏,如果不打印它就不能编译,如果不打印的话。我也想要调试级别,例如,如果我希望代码的性能关键部分不要在某些时候打印,而是在别人打印,我可以设置一个调试级别,并启动额外的调试打印。我遇到了一种实现调试级别的方法,确定是否打印甚至编译或不编译。我是这样实现的:

    DebugLog.h:

    // FILE: DebugLog.h
    // REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
    // debug logging, currently; in addition to disabling it.  Level 3 is the most information.
    // Levels 2 and 1 have progressively more.  Thus, you can write: 
    //     DEBUGLOG_LOG(1, "a number=%d", 7);
    // and it will be seen if DEBUG is anything other than undefined or zero.  If you write
    //     DEBUGLOG_LOG(3, "another number=%d", 15);
    // it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
    // to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
    // keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
    // maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
    // but the rest of the code is fully C compliant also if it is.
    
    #define DEBUG 1
    
    #ifdef DEBUG
    #define DEBUGLOG_INIT(filename) debuglog_init(filename)
    #else
    #define debuglog_init(...)
    #endif
    
    #ifdef DEBUG
    #define DEBUGLOG_CLOSE debuglog_close
    #else
    #define debuglog_close(...)
    #endif
    
    #define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)
    
    #if DEBUG == 0
    #define DEBUGLOG_LOG0(...)
    #endif
    
    #if DEBUG >= 1
    #define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
    #else
    #define DEBUGLOG_LOG1(...)
    #endif
    
    #if DEBUG >= 2
    #define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
    #else
    #define DEBUGLOG_LOG2(...)
    #endif
    
    #if DEBUG == 3
    #define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
    #else
    #define DEBUGLOG_LOG3(...)
    #endif
    
    void debuglog_init(char *filename);
    void debuglog_close(void);
    void debuglog_log(char* format, ...);
    

    DebugLog.cpp:

    // FILE: DebugLog.h
    // REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
    // debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
    // info.
    
    #include <stdio.h>
    #include <stdarg.h>
    
    #include "DebugLog.h"
    
    FILE *hndl;
    char *savedFilename;
    
    void debuglog_init(char *filename)
    {
        savedFilename = filename;
        hndl = fopen(savedFilename, "wt");
        fclose(hndl);
    }
    
    void debuglog_close(void)
    {
        //fclose(hndl);
    }
    
    void debuglog_log(char* format, ...)
    {
        hndl = fopen(savedFilename,"at");
        va_list argptr;
        va_start(argptr, format);
        vfprintf(hndl, format, argptr);
        va_end(argptr);
        fputc('\n',hndl);
        fclose(hndl);
    }
    

    使用宏

    要使用它,只需执行:

    DEBUGLOG_INIT("afile.log");
    

    要写入日志文件,只需执行以下操作:

    DEBUGLOG_LOG(1, "the value is: %d", anint);
    

    要关闭它,请执行以下操作:

    DEBUGLOG_CLOSE();
    

    虽然从技术上讲,目前这甚至不是必要的,因为它什么都不做。我现在仍然在使用CLOSE,但是,如果我改变主意它的工作原理,并希望在记录语句之间保持文件打开。

    然后,当你想打开调试打印时,只需编辑头文件中的第一个#define来表示,例如

    #define DEBUG 1
    

    要将日志记录语句编译为空,请执行

    #define DEBUG 0
    

    如果您需要经常执行的代码中的信息(即高级别的详细信息),您可能需要写一下:

     DEBUGLOG_LOG(3, "the value is: %d", anint);
    

    如果您将DEBUG定义为3,则记录级别1,2和&amp; 3编译。如果将其设置为2,则会获得1和1的记录级别。 2.如果将其设置为1,则只能获取日志级别1语句。

    对于do-while循环,由于这会求值为单个函数或者没有任何函数,而不是if语句,因此不需要循环。好吧,谴责我使用C而不是C ++ IO(和Qt的QString :: arg()是一种更安全的格式化变量的方式,在Qt中也是如此 - 它非常漂亮,但需要更多的代码和格式化文档不是有组织的 - 但我仍然发现它更可取的情况),但您可以将任何代码放在您想要的.cpp文件中。它也可能是一个类,但是你需要实例化它并跟上它,或者做一个new()并存储它。这样,您只需将#include,init和可选的close语句放入源代码中,就可以开始使用它了。如果你这么倾向,它会成为一个优秀的课程。

    我以前见过很多解决方案,但是没有一个符合我的标准。

    1. 可以扩展到你喜欢的任意级别。
    2. 如果不打印则会编译为空。
    3. 它将IO集中在一个易于编辑的位置。
    4. 使用printf格式设置非常灵活。
    5. 同样,它不会减慢调试运行速度,而始终编译调试打印始终在调试模式下执行。如果您正在进行计算机科学,并且不容易编写信息处理,您可能会发现自己正在运行一个消耗CPU的模拟器,例如,调试器使用索引超出向量的范围来停止它。这些在调试模式下已经非常缓慢地运行。数百个调试打印的强制执行必然会进一步降低这种运行速度。对我来说,这样的跑步并不少见。
    6. 不是非常重要,但另外:

      1. 没有参数就不需要打印(例如DEBUGLOG_LOG(3, "got here!"););从而允许你使用,例如Qt更安全的.arg()格式化。它适用于MSVC,因此可能是gcc。它使用##中的#define,这是非标准的,正如Leffler指出的那样,但得到广泛支持。 (如果需要,您可以重新编码,不要使用##,但是您必须使用他提供的黑客。)
      2. 警告:如果您忘记提供日志记录级别参数,MSVC无理由声明未定义标识符。

        您可能希望使用除DEBUG之外的预处理程序符号名称,因为某些源还定义了该符号(例如,使用./configure命令编写以准备构建)。当我开发它时,我似乎很自然。我在一个应用程序中开发它,其中DLL被其他东西使用,并且它更可以将日志打印发送到文件;但将其更改为vprintf()也可以正常工作。

        我希望这可以避免许多人为找出调试日志记录的最佳方法而感到悲伤;或者向您展示您可能更喜欢的。几十年来,我一直在半心半意地试图解决这个问题。适用于MSVC 2012&amp; 2015年,因此可能在gcc上;还有可能在许多其他人身上工作,但我还没有对他们进行过测试。

        我的意思是制作这一天的流媒体版本。

        注意:感谢Leffler,他亲切地帮助我更好地为StackOverflow格式化我的信息。

答案 9 :(得分:1)

我最喜欢的是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)

答案 10 :(得分:1)

因此,在使用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

您可以分析对什么表达式进行了评估。

它可以避免双重评估问题,但是缺少gensyms确实使它容易受到名称冲突的影响。

但是它确实嵌套:

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作为变量名,就可以了。

当然,我发现它(以及字符串的关联版本和调试级别的版本等)非常有价值。

答案 11 :(得分:0)

我相信这个主题的变体提供了调试类别,而不需要为每个类别分别设置一个宏名称。

我在Arduino项目中使用了这种变体,程序空间限制为32K,动态内存限制为2K。添加调试语句和跟踪调试字符串会快速占用空间。因此,每次构建代码时,必须能够将编译时包含的调试跟踪限制到最小值。

debug.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

调用.cpp文件

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...

答案 12 :(得分: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);

答案 13 :(得分:0)

如果你不关心输出到标准输出,你可以使用这个:

int doDebug = DEBUG;  // Where DEBUG may be supplied in compiler command
#define trace if (doDebug) printf

trace("whatever %d, %i\n", arg1, arg2);