2008-08-18 12 views
35

我想使用与printf相同的参数进行调试日志记录功能。但是在优化版本中可以被预处理器删除。如何创建一个只有调试变量参数列表的函数?像printf()

例如:

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

我看了复杂的宏,但这些并非适用于所有平台。 gcc支持他们,msvc不支持。

+0

斯图衍生它之前,MSVC确实支持可变参数的功能,它不支持可变参数宏。编辑:我的坏:在Visual C++ 2005中引入了对variadic宏的支持。 – hyperlogic 2008-08-18 22:15:41

+0

另请参阅[C`#define宏用于调试打印](https://stackoverflow.com/questions/1644868/c-define-macro-for -debug印刷)。请特别注意,通常最好确保编译器编译(但优化)代码,以便代码始终被检查,因此始终是正确的。否则,bit-rot会在十年后重新激活调试宏时设置,并且您会发现它不再编译。 – 2016-04-09 23:18:05

回答

22

通过定义一个与无操作或具有可变参数列表的函数调用相关的宏(下面的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 

那么可以清理了不少,但它的基本思想。

+0

优秀的答案,但是您应该使用** _ vsntprintf **实际上使其兼容Unicode。我添加了自己的版本,因为我需要预先安装一个字符串(如[DEBUG])。 http://vsackprint.com/zh/39186784/912236 – Orwellophile 2016-08-27 23:50:47

+0

`_vsnprintf()`特定于特定的(与Microsoft兼容的)实现 - 并且已被弃用。 `vsnprintf()`是标准的。 – Peter 2016-08-28 00:11:42

1

它们没有提供哪些平台? stdarg头文件是标准库的一部分:

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

任何平台都没有提供它不是一个标准的C实现(或非常,非常大)。对于这些,你将不得不使用可变参数:

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

+0

http://msdn.microsoft.com/en-us/library/kb57fad8(VS.71).aspx - 为了记录,这篇文章甚至向您展示了如何使用老派的可变参数作为奖励。 – Stu 2008-08-18 22:13:32

+0

看起来像在Visual C++ 2005中引入了对可变宏的支持。 – hyperlogic 2008-08-18 22:15:41

11

下面是我在C/C做点什么++。首先,你编写一个使用可变参数的函数(参见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")); 

所有你必须要记住的是调用调试功能时,使用双括号,以及整条生产线将在非调试代码移除。

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

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

4

另一种有趣的方式来存根出可变参数的功能是:

#define function sizeof 
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

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_ */ 
+0

使用“debug_fd”来保存文件指针而不是文件描述符似乎很奇怪;使用'debug_fp'会更传统。 – 2009-07-17 05:50:45

3

@CodingTheWheel:

有你的方法一个小问题。考虑一个调用,如

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

这在调试版本工作正常,但在发布版本将扩大到:

("x=%d", x); 

这是完全合法的C和将编译并通常没有侧运行 - 影响,但生成不必要的代码。我通常用它来消除这一问题的方法是:

  1. 充分利用X跟踪功能返回一个int(只返回0,则返回值并不重要)

  2. 变化在#中的#define else子句到:

    0 && XTrace 
    

现在的发行版将扩大到:

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

和任何像样的优化器都会丢掉整个东西,因为短路评估会阻止任何东西在执行之后发生。

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

20

这是我如何在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 & & COUT”注意< <具有更高的优先级比& & & &和短路评价,使整条生产线计算为0。由于不使用0,因此编译器根本不会为该行生成任何代码。

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); 
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标志被关闭时,它在运行时完全没有成本。

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 
} 

我说,“或多或少”,因为它不会自动ASCII字符串参数转换为WCHAR,但它应该让你出去的大多数的Unicode擦伤,而不必担心包裹在格式字符串TEXT()或L.

很大程度上从MSDN: Retrieving the Last-Error Code

相关问题