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

时间:2020-03-05 18:40:31  来源:igfitidea点击:

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

例如:

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

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

解决方案

回答

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

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

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

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

回答

这是我在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代码中删除。

回答

我仍然以旧的方式进行操作,方法是定义一个宏(下面的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

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

回答

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

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

回答

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

#define function sizeof

回答

在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相同的方式来实现类的自定义跟踪输出。

回答

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

回答

@CodingTheWheel:

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

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

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

("x=%d", x);

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

  • 使XTrace函数返回一个int(仅返回0,返回值无关紧要)
  • 将#else子句中的#define更改为:
0 && XTrace

现在,发行版本将扩展为:

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

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

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

回答

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

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

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

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

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

回答

看一下这个线程:

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

它应该回答问题。