前言
在C++里,在声明一个函数时可以显示给参数指定默认参数值:
voidadd(inta,intb=2)这样你直接调用add(1)编译器在构建的时候会默认给第二个参数传递2,但是C语言没有这样的能力,C语言是一个面向底层的语言,它不具备那么多花里胡哨的特性,但是我们可以通过宏的方法来实现,C语言里宏给我们提供了极大的灵活性开发,特别是在Linux、Zephyr这样的操作系统里经常会使用到宏来包装实现API。
宏的可变参数计算方法
我记得很久之前我看到过Zephyr的一段源码,它为unit test提供的一套API,是经过宏包装的,主要功能是通过宏来指定数量来生成指定数量级的测试函数,例如一个宏展3个Test函数,不需要自己去手写多个,其目的是为了测试一个API传递多个递阶数,所以这里先讲解一下如何利用宏来实现可变参数计算方法。
首先可以先看一段代码,这段代码是我最近为项目设计的日志模块所编写的,这里我简化了一下它的实现,目的是为了教学如何通过宏计算可变参数:
#defineFAST_LOG_PRINT_NUMBER(_0,_1,_2,_3,_4,_n,...)_n#defineFAST_LOG_MERG(...)FAST_LOG_PRINT_NUMBER(_,##__VA_ARGS__,\4,3,2,1,0)#defineFAST_LOG_CART_TRUE(a,b)a##b#defineFAST_LOG_CART(a,b)FAST_LOG_CART_TRUE(a,b)#defineFAST_LOG_PRINT(...)FAST_LOG_CART(FAST_LOG_PRINT_,\FAST_LOG_MERG(__VAR_ARGS__))(__VAR_ARGS__)#defineFAST_LOG_PRINT_0()//0#defineFAST_LOG_PRINT_1(_1)//1#defineFAST_LOG_PRINT_2(_1,_2)//2#defineFAST_LOG_PRINT_3(_1,_2,_3)//3#defineFAST_LOG_PRINT_4(_1,_2,_3,_4)//4这个宏计算最大支持4个参数传递同时也支持传递空参数,那么接下来来说一下这一段的实现原理,其核心函数就两个:
FAST_LOG_PRINT_NUMBER(_0,_1,_2,_3,_4,_n,...)_nFAST_LOG_MERG(...)FAST_LOG_PRINT_NUMBER(_,##__VA_ARGS__,\4,3,2,1,0)FAST_LOG_MERG输入的是可变参数,然后将__VA_ARGS__传递给FAST_LOG_PRINT_NUMBER,这个函数是推导宏,推导出有几个参数然后决定使用哪个FAST_LOG_PRINT,这里推导的思路也非常简单,FAST_LOG_MERG最终只输出_n,而FAST_LOG_MERG在调用FAST_LOG_PRINT_NUMBER的时候,假如给的是FAST_LOG_PRINT_NUMBER(0, 1, 2),那么最终宏翻译就是:FAST_LOG_PRINT_NUMBER(_, 0, 1, 2, 4, 3, 2) 2
最终这里的_n就变成了2,这里其实就是利用一个巧妙的挤数字方法,因为FAST_LOG_PRINT_NUMBER里面__VA_ARGS__后面紧跟着顺序数字依次递减,当__VA_ARGS__参数越多,后面的数字递增到N的就越大,这样就能计算出到底有多少个参数了。
其中最前面的__,这个是为了解决0参数时候也就是调用宏不给参数时默认给一个0,让_n这个数字变为0。
那么通过这个实现我们就可以实现给默认缺省参数了,在FAST_LOG_PRINT_0、FAST_LOG_PRINT_1、FAST_LOG_PRINT_2调用不同函数时来依次传递不同参数,例如有一个set_sys_config的api,可以封装为:
FAST_LOG_PRINT_0() set_sys_config(a, b, c, d)
#define FAST_LOG_PRINT_1(_1) set_sys_config(_1, a, b, c)
在调用时只需要调用FAST_LOG_PRINT即可:
FAST_LOG_PRINT(); //无参数
FAST_LOG_PRINT(“hello world”); //1个参数
这样就实现类似c++的缺省参数,其实这个方法在Linux/zephyr里也有用过,不过不是用于缺省,缺省只是这种开发方法应用场景的其中一个,在内核里多数用于例如写test case,要写许多非常多的重复代码,那么这个时候就可以利用这种方法来动态生成不同参数级别的test case func。