关于c ++:如何创建带有可变参数列表的仅调试函数? 就像printf()

关于c ++:如何创建带有可变参数列表的仅调试函数? 就像printf()

How do you create a debug only function that takes a variable argument list? Like printf()

我想使用与printf相同的参数来制作调试日志记录功能。 但是可以在优化的构建过程中由预处理器删除。

例如:

1
2
Debug_Print("Warning: value %d > 3!
"
, value);

我看过可变参数宏,但并非在所有平台上都可用。 gcc支持它们,msvc不支持。


我仍然通过定义宏(下面的XTRACE)将其与无操作或具有可变参数列表的函数相关联,以旧方式进行操作。在内部,调用vsnprintf,以便您可以保留printf语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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开关:

1
2
3
4
5
#ifdef _DEBUG
#define XTRACE XTrace
#else
#define XTRACE
#endif

好了,可以清理很多,但这是基本思想。


这就是我在C ++中调试打印输出的方式。像这样定义" dout"(调试):

1
2
3
4
5
#ifdef DEBUG
#define dout cout
#else
#define dout 0 && cout
#endif

在代码中,我像使用" cout"一样使用" dout"。

1
2
dout <<"in foobar with x=" << x <<" and y=" << y << '
'
;

如果预处理器将'dout'替换为'0 && cout',请注意<<具有比&&更高的优先级,并且&&的短路求值使整行的求值为0。由于未使用0,因此编译器根本不生成任何代码。对于那条线。


这是我在C / C ++中所做的事情。首先,您编写一个使用varargs内容的函数(请参见Stu文章中的链接)。然后执行以下操作:

1
2
3
4
5
6
7
8
9
 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
"
,"any number","of args" ));

您需要记住的是在调用调试函数时使用双括号,并且整个行将以非DEBUG代码删除。


可变参数函数的另一个有趣的方法是:

1
#define function sizeof

@CodingTheWheel:

您的方法存在一个小问题。考虑一个呼叫,例如

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

这在调试版本中工作正常,但在发行版本中它将扩展为:

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

这是完全合法的C语言,可以编译并且通常运行时没有副作用,但是会生成不必要的代码。我通常用来消除该问题的方法是:

  • 使XTrace函数返回一个int(仅返回0,返回值无关紧要)

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

    1
    0 && XTrace
  • 现在发行版本将扩展为:

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

    而且任何合适的优化程序都将丢弃整个过程,因为短路评估会阻止&&之后的任何事情执行。

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


    在C ++中,您可以使用流运算符简化操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #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

    并像这样使用它:

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

    然后,您可以按照与输出到std::cout相同的方式来实现类的自定义跟踪输出。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #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);
    }

    然后将整个内容包装在宏中。


    看一下这个线程:

    • 如何制作可变参数宏(可变数量的参数)

    它应该回答您的问题。


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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #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_ */

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

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

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #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:检索上一个错误代码


    这是我用的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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标志关闭时,在运行时绝对不会花费任何费用。


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

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

    然后,您可以像下面这样调用函数:

    1
    2
    3
        int value = 42;
        DLog(L"The answer is: %d
    "
    , value);


    推荐阅读

      excel怎么用乘法函数

      excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

      opporeno8参数配置及价格

      opporeno8参数配置及价格,面部,亿元,Oppo的荣誉2020年1月4日,接近屏幕关闭传感器是否支持双卡:支持oppor11splus什么时候上市的Oppo R11S P

      excel中乘法函数是什么?

      excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

      魅蓝note6性能参数有哪些

      魅蓝note6性能参数有哪些,摄像头,蓝牙,魅蓝note6性能参数有哪些魅力蓝色Note6最好拍照。电池寿命更长。蓝色Note6使用高通 snapdragon 625

      标准差excel用什么函数?

      标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

      设置总账参数|用友u8设置总账参数

      设置总账参数|用友u8设置总账参数,,1. 用友u8设置总账参数1、首先要点开数据权限控制设置;2、选择想要设置控制的单据;3、打开后看到左上角

      csgo参数设置|csgo怎么保存

      csgo参数设置|csgo怎么保存,,csgo怎么保存第一步下载csgo的官方版本。然后再下载一个5e对战平台,PS:5e的账号和csgo的账号不是一个账号。第

      移动apn设置|移动apn设置参数

      移动apn设置|移动apn设置参数,,移动apn设置参数1、打开手机系统设置界面应用,点击页面中的“移动网络”设置选项。2、进入移动网络设置页面