不定参数宏
引入
在工程代码中,都会有较为规范的日志信息,除了表达函数是否正常执行、变量值为多少等调试信息外,还有功能模块、打印等级(debug打印、error打印)、所在文件、行数以及函数等信息。
有两种方法可实现, 如下:
- 定义自己的打印函数
可以在函数内加入时间、文件名、行号以及函数名等信息。void print_log(int functionModule, int printLevel, char *format, ...);
- 定义各个功能模块的日志信息不定参数宏
虽然还是通过不定参数函数实现,但是通过给不同功能模块定义不同的打印宏,使得日志信息表达含义更加明确。/*FTP为宏定义*/ #define FTP_DEBUG(level, arg...) DEBUG(FTP, level, ##arg) #define DEBUG(module, level, format, ...) print_log(module, level, format, ##__VA_ARGS__);
是什么
通过把宏参数列表中最后的参数写成省略号(...),使其可以接受数量可变的宏参数。
表现形式
- 不定参数宏__VA_ARGS__(C99)
#define DEBUG(...) print(__VA_ARGS__)
- GCC的复杂宏
替换效果如下:#define DEBUG(arg...) print(arg)
DEBUG("hello") = print("hello") DEBUG("[%s][%zu][%s]", file, line, function) = print("[%s][%zu][%s]", file, line, function)
使用方法
头文件strarg.h
中定义了一组对象、方法使得我们可以使用不定参数。
- va_list ap:用于储存省略部分数据的对象类型
- va_start(format, ap):使得ap指向format后的不定参数列表,即不定参数列表中的第一个参数
- int tmp = va_arg(ap, int):将当前ap指向的值返回,并使ap指向参数列表中的下一个参数,va_arg中第二个参数类型名要与返回值类型相同
- va_end(ap):完成清理工作,释放动态分配申请的用于存储参数的内存
其他注意事项
- ##的使用
在一些情景下,如下:
当调用为#define DEBUG(format, ...) print(format, ##__VA_ARG__)
DEBUG("hello")
时,此时可变参数部分为空,那么展开为print("hello",)
,出现多余的,
,编译器将会报错,而##的作用就是在这种情况下消除多余的,
。
示例
...
FTP_DEBUG(DEBUG, "this is a test.\n");
...
#define FTP_DEBUG(level, arg...) PRINT(FTP, level, ##arg)
#define PRINT(module, level, format, ...) print_log_info(module, level, __FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
...
/**@brief: 打印日志信息(系统信息 + 调试信息)
* @param[i]: 功能模块序号,int moduleIndex
* @param[i]: 打印登记序号,int printLevelIndex
* @param[i]: 打印日志所在文件,char *file
* @param[i]: 打印日志所在行号,size_t line
* @param[i]: 打印日志所在函数,char *function
* @param[i]: 调试信息,char *format
* @return: 无
*/
void print_log_info(int moduleIndex, int printLevelIndex, char *file, size_t line, char *function, char *format, ...)
{
char logInfo[LOG_INFO_LENGTH] = {0};
char debugInfo[DEBUG_INFO_LENGTH] = {0};
struct tm currentLocalTime;
va_list ap;
if (is_value_invaild(moduleIndex, MAIN, MAX_MODULE_INDEX) \
|| is_value_invaild(printLevelIndex, DEBUG, MAX_PRINT_LEVEL_INDEX) \
|| NULL == file || NULL == function || NULL == format)
{
printf("print_log_info, input param is invalid.\n");
return;
}
memset(¤tLocalTime, 0, sizeof(currentLocalTime));
if (get_local_time(¤tLocalTime))
{
printf("get_local_time failed.\n");
return;
}
/*整理系统信息*/
(void)snprintf(logInfo, sizeof(logInfo), \
"[%02d/%02d %02d:%02d:%02d][%s][%s][%s][%zu][%s]", \
currentLocalTime.tm_mon + 1, currentLocalTime.tm_mday, \
currentLocalTime.tm_hour, currentLocalTime.tm_min, currentLocalTime.tm_sec, \
g_module_atrr[moduleIndex].name, g_print_level_atrr[printLevelIndex].name, \
file, line, function);
/*获取调试信息*/
va_start(ap, format);
(void)vsnprintf(debugInfo, sizeof(debugInfo), format, ap);
/*打印日志信息*/
(void)strncat(logInfo, debugInfo, sizeof(debugInfo));
printf("%s", logInfo);
return;
}