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

时间:2008-08-18 21:22:21

标签: c++ c c-preprocessor

我想使用与printf相同的参数创建调试日志记录功能。但是在优化构建期间可以由预处理器删除的那个。

例如:

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

我查看了可变参数宏,但这些宏并不适用于所有平台。 gcc支持他们,msvc没有。

14 个答案:

答案 0 :(得分:23)

我仍然按照旧的方式,通过定义一个宏(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

那可以清理一下,但这是基本的想法。

答案 1 :(得分:21)

这就是我在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&amp;&amp; cout'注意&lt;&lt;优先级高于&amp;&amp; &amp;&amp; amp;的短路评估使整行评估为0.由于未使用0,编译器根本不会为该行生成任何代码。

答案 2 :(得分:11)

这是我在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" ));

所有你必须记住的是在调用debug函数时使用double-parens,并且整行将在非DEBUG代码中删除。

答案 3 :(得分:4)

另一种有效的可变函数的方法是:

#define function sizeof

答案 4 :(得分:3)

@CodingTheWheel:

您的方法存在一个小问题。考虑诸如

之类的电话
XTRACE("x=%d", x);

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

("x=%d", x);

哪个是完全合法的C并且将编译并且通常在没有副作用的情况下运行但是生成不必要的代码。我通常用来消除这个问题的方法是:

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

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

    0 && XTrace
    
  3. 现在发布版本将扩展为:

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

    并且任何体面的优化器都会抛弃整个东西,因为短路评估会阻止&amp;&amp; amp;永远被执行。

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

答案 5 :(得分:2)

啊,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);
}

然后将整个事物包装在一个宏中。

答案 6 :(得分:2)

在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的方式非常相似。

答案 7 :(得分:1)

他们没有哪些平台? stdarg是标准库的一部分:

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

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

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

答案 8 :(得分:1)

这种功能的部分问题在于它经常需要 可变宏。这些都是最近标准化的(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_ */

答案 9 :(得分:1)

看一下这个帖子:

它应该回答你的问题。

答案 10 :(得分:0)

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

    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);

答案 11 :(得分:0)

这就是我使用的:

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)

这是用户答案的​​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
}

我说,&#34;或多或少&#34;,因为它不会自动将ASCII字符串参数转换为WCHAR,但它应该让你摆脱大多数Unicode擦除而不必担心将格式字符串包装在TEXT()中或在其前面用L。

主要来自MSDN: Retrieving the Last-Error Code

答案 13 :(得分:0)

问题不完全是什么。但是此代码对于调试目的将是有用的,它将打印每个变量的值及其名称。这是完全类型无关的,并且支持可变数量的参数。 甚至可以很好地显示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