【C】用可变参宏增强调试输出
# 前言
在大型项目中使用`printf`大法进行调试的时候,如果能同时输出所在行,那么分析log就会简单得多。
# 自制`printf`
首先自制一个`printf`,要加上行号和文件名。
```C
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
int vprintf2(const char* src_fn,const int src_ln,const char* format,va_list arg_list)
{
char buff; // 记得调整缓冲区大小!
int a=snprintf(buff,sizeof(buff),"[%s@%d] ",src_fn,src_ln);
int b=vsnprintf(&buff,sizeof(buff)-a,format,arg_list);
// 可以把fwrite替换成别的输出,比如OutputDebugStringA
fwrite(buff,sizeof(char),a+b,stdout);
return a+b;
}
int printf2(const char* src_fn,const int src_ln,const char* format,...)
{
int ret;
va_list arg_list;
va_start(arg_list,format);
ret=vprintf2(src_fn,src_ln,format,arg_list);
va_end(arg_list);
return ret;
}
```
调用`printf2`就这么写:
```C
printf2(__FILE__,__LINE__,"Hello World!");
```
但是每次调用`printf2`都要加上`__FILE__`和`__LINE__`就很蛋疼,而且我们还不能在`vprintf2`的实现中直接放这两个宏,否则log中的文件名和行号永远是vprintf2这个函数的位置。
# 宏包装
不过我们可以用宏来包装`printf2`:
```C
#define printf3(fmt,...) printf2(__FILE__,__LINE__,fmt,__VA_ARGS__)
```
这个宏定义在MSVC是没问题的,但是在GCC和clang里用是有坑的。
举个例子,`printf3("Hello");`在宏展开时会变成`printf2(__FILE__,__LINE__,"Hello",);`,注意括号前多出了一个逗号。这是因为我们没有使用额外的参数所导致的。**而MSVC允许在括号前多出一个逗号**,因此仍然能编译通过。
要让GCC和clang对这种情况进行特殊处理,需要改变`__VA_ARGS__`的使用方式:
```C
#define printf3(fmt,...) printf2(__FILE__,__LINE__,fmt,##__VA_ARGS__)
```
虽然(https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html),但是**MSVC也支持这个语法**。因此直接用上述的套路包装`printf`即可。
# 安全隐患
在对外发布的时候,程序输出的日志是不可以泄漏源文件的信息的,因此需要用条件编译把`printf3`包装起来,使得对外发布的版本里不包含源文件信息。
```C
#if defined(_DEBUG)
#define printf3(fmt,...) printf2(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#else
#define printf3(fmt,...) printf(fmt,##__VA_ARGS__)
#endif
```
如果只是调试输出,早期版本可以这么做,毕竟不支持可变参数宏
#define tr printf("[%s@%d]:",__FILE__ , __LINE__);printf Release模式可以考虑直接 #define printf3(fmt, ...) 0 //什么都不输出,直接返回0(因为printf函数返回值是int类型)。 可惜早期c标准不支持可变参宏 刚看到https://www.zhihu.com/question/552041042
说 C23 支持这样写:
#define debug(format, …) fprintf(stderr, format __VA_OPT__(,) __VA_ARGS__)
页:
[1]