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代码删除。
可变参数函数的另一个有趣的方法是:
@CodingTheWheel:
您的方法存在一个小问题。考虑一个呼叫,例如
这在调试版本中工作正常,但在发行版本中它将扩展为:
这是完全合法的C语言,可以编译并且通常运行时没有副作用,但是会生成不必要的代码。我通常用来消除该问题的方法是:
使XTrace函数返回一个int(仅返回0,返回值无关紧要)
将#else子句中的#define更改为:
现在发行版本将扩展为:
而且任何合适的优化程序都将丢弃整个过程,因为短路评估会阻止&&之后的任何事情执行。
当然,就像我写的最后一句话一样,我意识到也许原始形式也可能被优化,并且在副作用的情况下,例如将函数调用作为参数传递给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); |
|