代码之家  ›  专栏  ›  技术社区  ›  hyperlogic

如何创建一个只接受变量参数列表的调试函数?类似printf()

  •  43
  • hyperlogic  · 技术社区  · 17 年前

    我想制作一个调试日志函数,其参数与 printf 但在优化构建过程中,预处理器可以删除它。

    例如:

    Debug_Print("Warning: value %d > 3!\n", value);
    

    我研究过可变宏,但这些宏并非在所有平台上都可用。 gcc 支持他们, msvc 没有。

    14 回复  |  直到 17 年前
        1
  •  24
  •   Gunther Struyf    10 年前

    我仍然采用旧的方法,通过定义一个宏(下面的XTRACE),该宏与无操作或带有变量参数列表的函数调用相关联。在内部,调用vsnprintf以便保留printf语法:

    #include <stdio.h>
    
    void XTrace0(LPCTSTR lpszText)
    {
       ::OutputDebugString(lpszText);
    }
    
    void XTrace(LPCTSTR lpszFormat, ...)
    {
        va_list args;
        va_start(args, lpszFormat);
        int nBuf;
        TCHAR szBuffer[512]; // get rid of this hard-coded buffer
        nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
        ::OutputDebugString(szBuffer);
        va_end(args);
    }
    

    然后是一个典型的#ifdef开关:

    #ifdef _DEBUG
    #define XTRACE XTrace
    #else
    #define XTRACE
    #endif
    

    嗯,这可以清理很多,但这是基本的想法。

        2
  •  22
  •   sdcvvc    14 年前

    这就是我在C++中调试打印输出的方法。定义“dout”(调试输出)如下:

    #ifdef DEBUG
    #define dout cout
    #else
    #define dout 0 && cout
    #endif
    

    在代码中,我使用“dout”就像“cout”一样。

    dout << "in foobar with x= " << x << " and y= " << y << '\n';
    

    如果预处理器将“dout”替换为“0&&不能注意到<<优先级高于&&以及短路评估;&使整条线的计算结果为0。由于没有使用0,编译器根本不会为该行生成代码。

        3
  •  11
  •   Graeme Perrow    17 年前

    这是我用C/C++做的一些事情。首先,您编写一个使用varargs内容的函数(请参阅Stu帖子中的链接)。然后做这样的事情:

    
     int debug_printf( const char *fmt, ... );
     #if defined( DEBUG )
      #define DEBUG_PRINTF(x) debug_printf x
     #else
       #define DEBUG_PRINTF(x)
     #endif
    
     DEBUG_PRINTF(( "Format string that takes %s %s\n", "any number", "of args" ));
    

    您只需在调用调试函数时使用双括号,整行代码将在非调试代码中被删除。

        4
  •  4
  •   Tim Cooper    14 年前

    啊,vsprintf()是我缺少的东西。我可以用它将变量参数列表直接传递给printf():

    #include <stdarg.h>
    #include <stdio.h>
    
    void DBG_PrintImpl(char * format, ...)
    {
        char buffer[256];
        va_list args;
        va_start(args, format);
        vsprintf(buffer, format, args);
        printf("%s", buffer);
        va_end(args);
    }
    

    然后把整个东西包在一个宏中。

        5
  •  4
  •   Jonathan Leffler    10 年前

    另一种删除可变函数的有趣方法是:

    #define function sizeof
    
        6
  •  3
  •   Ferruccio    17 年前

    @对车轮进行编码:

    你的方法有一个小问题。考虑一个电话,例如

    XTRACE("x=%d", x);
    

    这在调试版本中运行良好,但在发布版本中,它将扩展为:

    ("x=%d", x);
    

    这是完全合法的C语言,编译和运行通常没有副作用,但会生成不必要的代码。我通常用来解决这个问题的方法是:

    1. 让XTrace函数返回一个int(只返回0,返回值无关紧要)

    2. 将#else子句中的#define更改为:

      0 && XTrace
      

    现在发布版本将扩展到:

    0 && XTrace("x=%d", x);
    

    任何一个像样的优化器都会扔掉整个东西,因为短路评估会阻止在&&永远不会被处决。

    当然,就在我写最后一句话的时候,我意识到也许原始形式也可能被优化掉,在出现副作用的情况下,比如作为参数传递给XTrace的函数调用,这可能是一个更好的解决方案,因为它将确保调试和发布版本的行为相同。

        7
  •  2
  •   Tim Cooper    14 年前

    在C++中,你可以使用流运算符来简化事情:

    #if defined _DEBUG
    
    class Trace
    {
    public:
       static Trace &GetTrace () { static Trace trace; return trace; }
       Trace &operator << (int value) { /* output int */ return *this; }
       Trace &operator << (short value) { /* output short */ return *this; }
       Trace &operator << (Trace &(*function)(Trace &trace)) { return function (*this); }
       static Trace &Endl (Trace &trace) { /* write newline and flush output */ return trace; }
       // and so on
    };
    
    #define TRACE(message) Trace::GetTrace () << message << Trace::Endl
    
    #else
    #define TRACE(message)
    #endif
    

    并像这样使用它:

    void Function (int param1, short param2)
    {
       TRACE ("param1 = " << param1 << ", param2 = " << param2);
    }
    

    然后,您可以为类实现定制的跟踪输出,就像输出到 std::cout .

        8
  •  1
  •   Stu    17 年前

    它们在哪些平台上不可用?stdarg是标准库的一部分:

    http://www.opengroup.org/onlinepubs/009695399/basedefs/stdarg.h.html

    任何不提供它的平台都不是标准的C实现(或者非常非常古老)。对于这些,你必须使用varargs:

    http://opengroup.org/onlinepubs/007908775/xsh/varargs.h.html

        9
  •  1
  •   Jonathan Leffler    17 年前

    这种功能的部分问题在于,它通常需要 可变宏。这些是最近才标准化的(C99) 旧的C编译器不支持该标准,或者有自己的特殊工作 周围。

    下面是我写的一个调试头,它有几个很酷的功能:

    • 支持调试宏的C99和C89语法
    • 基于函数参数启用/禁用输出
    • 输出到文件描述符(文件io)

    注意:由于某种原因,我遇到了一些轻微的代码格式问题。

    #ifndef _DEBUG_H_
    #define _DEBUG_H_
    #if HAVE_CONFIG_H
    #include "config.h"
    #endif
    
    #include "stdarg.h"
    #include "stdio.h"
    
    #define ENABLE 1
    #define DISABLE 0
    
    extern FILE* debug_fd;
    
    int debug_file_init(char *file);
    int debug_file_close(void);
    
    #if HAVE_C99
    #define PRINT(x, format, ...) \
    if ( x ) { \
    if ( debug_fd != NULL ) { \
    fprintf(debug_fd, format, ##__VA_ARGS__); \
    } \
    else { \
    fprintf(stdout, format, ##__VA_ARGS__); \
    } \
    }
    #else
    void PRINT(int enable, char *fmt, ...);
    #endif
    
    #if _DEBUG
    #if HAVE_C99
    #define DEBUG(x, format, ...) \
    if ( x ) { \
    if ( debug_fd != NULL ) { \
    fprintf(debug_fd, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
    } \
    else { \
    fprintf(stderr, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
    } \
    }
    
    #define DEBUGPRINT(x, format, ...) \
    if ( x ) { \
    if ( debug_fd != NULL ) { \
    fprintf(debug_fd, format, ##__VA_ARGS__); \
    } \
    else { \
    fprintf(stderr, format, ##__VA_ARGS__); \
    } \
    }
    #else /* HAVE_C99 */
    
    void DEBUG(int enable, char *fmt, ...);
    void DEBUGPRINT(int enable, char *fmt, ...);
    
    #endif /* HAVE_C99 */
    #else /* _DEBUG */
    #define DEBUG(x, format, ...)
    #define DEBUGPRINT(x, format, ...)
    #endif /* _DEBUG */
    
    #endif /* _DEBUG_H_ */
    
        10
  •  1
  •   Community Mohan Dere    9 年前

    看看这个帖子:

    它应该回答你的问题。

        11
  •  0
  •   mousomer    12 年前

    这是我使用的:

    inline void DPRINTF(int level, char *format, ...)
    {
    #    ifdef _DEBUG_LOG
            va_list args;
            va_start(args, format);
            if(debugPrint & level) {
                    vfprintf(stdout, format, args);
            }
            va_end(args);
    #    endif /* _DEBUG_LOG */
    }
    

    当_DEBUG_LOG标志关闭时,这在运行时绝对不花费任何成本。

        12
  •  0
  •   Orwellophile    9 年前

    这是用户答案的TCHAR版本,因此它将作为ASCII码工作( 正常的 ),或Unicode模式(或多或少)。

    #define DEBUG_OUT( fmt, ...) DEBUG_OUT_TCHAR(       \
                TEXT(##fmt), ##__VA_ARGS__ )
    #define DEBUG_OUT_TCHAR( fmt, ...)                  \
                Trace( TEXT("[DEBUG]") #fmt,            \
                ##__VA_ARGS__ )
    void Trace(LPCTSTR format, ...)
    {
        LPTSTR OutputBuf;
        OutputBuf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT,   \
                (size_t)(4096 * sizeof(TCHAR)));
        va_list args;
        va_start(args, format);
        int nBuf;
        _vstprintf_s(OutputBuf, 4095, format, args);
        ::OutputDebugString(OutputBuf);
        va_end(args);
        LocalFree(OutputBuf); // tyvm @sam shaw
    }
    

    我说“或多或少”,因为它不会自动将ASCII字符串参数转换为WCHAR,但它应该能让你摆脱大多数Unicode抓取,而不必担心将格式字符串包装在TEXT()中或前面加L。

    主要来源于 MSDN: Retrieving the Last-Error Code

        13
  •  0
  •   parth_07    6 年前

    问题中问的不完全是什么。但这段代码将有助于调试,它将打印每个变量的值及其名称。这是完全独立于类型的,支持可变数量的参数。 甚至可以很好地显示STL的值,前提是您为它们重载了输出运算符

    #define show(args...) describe(#args,args);
    template<typename T>
    void describe(string var_name,T value)
    {
        clog<<var_name<<" = "<<value<<" ";
    }
    
    template<typename T,typename... Args>
    void describe(string var_names,T value,Args... args)
    {
        string::size_type pos = var_names.find(',');
        string name = var_names.substr(0,pos);
        var_names = var_names.substr(pos+1);
        clog<<name<<" = "<<value<<" | ";
        describe(var_names,args...);
    }
    

    样品使用:

    int main()
    {
        string a;
        int b;
        double c;
        a="string here";
        b = 7;
        c= 3.14;
        show(a,b,c);
    }
    

    输出:

    a = string here | b = 7 | c = 3.14 
    
        14
  •  0
  •   Koffiman    5 年前

    今天遇到了这个问题,我的解决方案是以下宏观:

        static TCHAR __DEBUG_BUF[1024];
        #define DLog(fmt, ...)  swprintf(__DEBUG_BUF, fmt, ##__VA_ARGS__); OutputDebugString(__DEBUG_BUF);
      
    

    然后,您可以这样调用该函数:

        int value = 42;
        DLog(L"The answer is: %d\n", value);