可变参数宏
问题起源
在阅读NRF52832的官方程序demo时,发现有个自定义的log文件,程序运行时log通过jlink输出至j-link viewr显示。
代码解析
查看NRF_LOG_DEBUG
的定义,步步深入依次为多层宏定义,具体如下
//nrf_log.h
#define NRF_LOG_DEBUG(...) NRF_LOG_INTERNAL_DEBUG( __VA_ARGS__)
//nrf_log_internal.h
#define NRF_LOG_INTERNAL_DEBUG(...) \
NRF_LOG_INTERNAL_MODULE(NRF_LOG_SEVERITY_DEBUG, NRF_LOG_SEVERITY_DEBUG, __VA_ARGS__)
#define NRF_LOG_INTERNAL_MODULE(level, level_id, ...) \
if (NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= level) && \
(level <= NRF_LOG_DEFAULT_LEVEL)) \
{ \
if (NRF_LOG_FILTER >= level) \
{ \
LOG_INTERNAL(LOG_SEVERITY_MOD_ID(level_id), __VA_ARGS__); \
} \
}
#define LOG_INTERNAL(type, ...) LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( \
__VA_ARGS__), type, __VA_ARGS__)
#define LOG_INTERNAL_X(N, ...) CONCAT_2(LOG_INTERNAL_, N) (__VA_ARGS__)
//nordic_common.h
#define CONCAT_2(p1, p2) CONCAT_2_(p1, p2)
#define CONCAT_2_(p1, p2) p1##p2
分层查看改定义
1. 第一层
#define NRF_LOG_DEBUG(...) NRF_LOG_INTERNAL_DEBUG( __VA_ARGS__)
上述语句使用了c语言中可变参数宏,前一个括号中三个点**…**缺省号代表一个可以变化的参数表。后一个括号中 VA_ARGS 为保留名,作用是把参数传递给宏。当宏的调用展开时,实际的参数就传递给NRF_LOG_INTERNAL_DEBUG 了。
比如调用
NRF_LOG_DEBUG(“cnt= %d/n”, cnt);
而处理器会把宏的调用替换成:
NRF_LOG_INTERNAL_DEBUG (“cnt= %d/n”, cnt);
可变参数支持在每一次调用中传递不同数目的参数,比如调用一个参数也可以。
NRF_LOG_DEBUG(“helloword”);
1. 第二层
//nrf_log_internal.h
#define NRF_LOG_INTERNAL_DEBUG(...) \
NRF_LOG_INTERNAL_MODULE(NRF_LOG_SEVERITY_DEBUG, NRF_LOG_SEVERITY_DEBUG, __VA_ARGS__)
与第一层相同,同样使用了可变参数宏,区别是第二层入参多了两个参数
** NRF_LOG_SEVERITY_DEBUG** ,定义为:
typedef enum
{
NRF_LOG_SEVERITY_NONE,
NRF_LOG_SEVERITY_ERROR,
NRF_LOG_SEVERITY_WARNING,
NRF_LOG_SEVERITY_INFO,
NRF_LOG_SEVERITY_DEBUG,
NRF_LOG_SEVERITY_INFO_RAW, /* Artificial level to pass information about skipping string postprocessing.*/
} nrf_log_severity_t;
他是一个枚举变量,按照字面含义应该是定义日志输出级别,用于过滤筛选,从上到下依次变大 0-5。再下一层,利用这个参数进行输出过滤。
#define NRF_LOG_INTERNAL_MODULE(level, level_id, ...) \
if (NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= level) && \
(level <= NRF_LOG_DEFAULT_LEVEL)) \
{ \
if (NRF_LOG_FILTER >= level) \
{ \
LOG_INTERNAL(LOG_SEVERITY_MOD_ID(level_id), __VA_ARGS__); \
} \
}
NRF_LOG_ENABLED
为日志输出总开关是sdk_config.h
文件中一个宏定义,如果关闭,下方的日志都不会输出。
//sdk_config.h
#ifndef NRF_LOG_ENABLED
#define NRF_LOG_ENABLED 0
#endif
NRF_LOG_LEVEL
仍为宏定义,为日志等级
//nrf_log.h
#ifndef NRF_LOG_LEVEL
#define NRF_LOG_LEVEL NRF_LOG_DEFAULT_LEVEL
#endif
//sdk_config.h
// <o> NRF_LOG_DEFAULT_LEVEL - Default Severity level
// <0=> Off
// <1=> Error
// <2=> Warning
// <3=> Info
// <4=> Debug
#ifndef NRF_LOG_DEFAULT_LEVEL
#define NRF_LOG_DEFAULT_LEVEL 3
#endif
NRF_LOG_FILTER
为过滤宏定义
#if NRF_LOG_FILTERS_ENABLED && NRF_LOG_ENABLED
#define NRF_LOG_FILTER NRF_LOG_ITEM_DATA_DYNAMIC(NRF_LOG_MODULE_NAME).filter
#define NRF_LOG_INST_FILTER(p_inst) (p_inst)->filter
#else
#undef NRF_LOG_FILTER
#define NRF_LOG_FILTER NRF_LOG_SEVERITY_DEBUG
#define NRF_LOG_INST_FILTER(p_inst) NRF_LOG_SEVERITY_DEBUG
#endif
整个功能可以描述为,当日志输出使能,且入参level等级小于默认日志等级和设定等级,且小于过滤等级才会调用内部的LOG_INTERNAL
语句。
第三层及后续层
#define LOG_INTERNAL(type, ...) LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( \
__VA_ARGS__), type, __VA_ARGS__)
#define LOG_INTERNAL_X(N, ...) CONCAT_2(LOG_INTERNAL_, N) (__VA_ARGS__)
//nordic_common.h
#define CONCAT_2(p1, p2) CONCAT_2_(p1, p2)
#define CONCAT_2_(p1, p2) p1##p2
到这一步,从最底层往上看,宏定义**##**双井号的作用是将前后两个字符连接起来,那么CONCAT_2_
左右就是将两个入参拼接在一起,LOG_INTERNAL_X
入参为N,也就是LOG_INTERNAL_X
的调用变为(LOG_INTERNAL_N)(__VA_ARGS)
,LOG_INTERNAL_N
是实际最终调用的输出函数名,那LOG_INTERNAL_N
到底是什么呢,我们把他称为目标输出函数
接着往下看,
NUM_VA_ARGS_LESS_1( __VA_ARGS__),
的作用是获取可变参数的总数量,定义如下:
/**@brief Macro to get the number of arguments in a call variadic macro call.
* First argument is not counted.
*
* param[in] ... List of arguments
*
* @retval Number of variadic arguments in the argument list
*/
#define NUM_VA_ARGS_LESS_1(...) NUM_VA_ARGS_LESS_1_IMPL(__VA_ARGS__, 63, 62, 61, \
60, 59, 58, 57, 56, 55, 54, 53, 52, 51, \
50, 49, 48, 47, 46, 45, 44, 43, 42, 41, \
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, \
30, 29, 28, 27, 26, 25, 24, 23, 22, 21, \
20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ~)
/**@brief Implementation details for NUM_VAR_ARGS */
#define NUM_VA_ARGS_LESS_1_IMPL( \
_ignored, \
_0, _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, N, ...) N
也就是传入下一层的N
,可以为为0-63,任意数字,到此终于明白,目标输出函数
为LOG_INTERNAL_1
,LOG_INTERNAL_2
到LOG_INTERNAL_64
,在nrf_log_intternal.h
中也有这些函数的定义
#if NRF_LOG_ENABLED
#define NRF_LOG_INTERNAL_LOG_PUSH(_str) nrf_log_push(_str)
#define LOG_INTERNAL_0(type, str) \
nrf_log_frontend_std_0(type, str)
#define LOG_INTERNAL_1(type, str, arg0) \
/*lint -save -e571*/nrf_log_frontend_std_1(type, str, (uint32_t)(arg0))/*lint -restore*/
#define LOG_INTERNAL_2(type, str, arg0, arg1) \
/*lint -save -e571*/nrf_log_frontend_std_2(type, str, (uint32_t)(arg0), \
(uint32_t)(arg1))/*lint -restore*/
#define LOG_INTERNAL_3(type, str, arg0, arg1, arg2) \
/*lint -save -e571*/nrf_log_frontend_std_3(type, str, (uint32_t)(arg0), \
(uint32_t)(arg1), (uint32_t)(arg2))/*lint -restore*/
#define LOG_INTERNAL_4(type, str, arg0, arg1, arg2, arg3) \
/*lint -save -e571*/nrf_log_frontend_std_4(type, str, (uint32_t)(arg0), \
(uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3))/*lint -restore*/
#define LOG_INTERNAL_5(type, str, arg0, arg1, arg2, arg3, arg4) \
/*lint -save -e571*/nrf_log_frontend_std_5(type, str, (uint32_t)(arg0), \
(uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3), (uint32_t)(arg4))/*lint -restore*/
#define LOG_INTERNAL_6(type, str, arg0, arg1, arg2, arg3, arg4, arg5) \
/*lint -save -e571*/nrf_log_frontend_std_6(type, str, (uint32_t)(arg0), \
(uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3), (uint32_t)(arg4), (uint32_t)(arg5))/*lint -restore*/
#else //NRF_LOG_ENABLED
#define NRF_LOG_INTERNAL_LOG_PUSH(_str) (void)(_str)
#define LOG_INTERNAL_0(_type, _str) \
(void)(_type); (void)(_str)
#define LOG_INTERNAL_1(_type, _str, _arg0) \
(void)(_type); (void)(_str); (void)(_arg0)
#define LOG_INTERNAL_2(_type, _str, _arg0, _arg1) \
(void)(_type); (void)(_str); (void)(_arg0); (void)(_arg1)
#define LOG_INTERNAL_3(_type, _str, _arg0, _arg1, _arg2) \
(void)(_type); (void)(_str); (void)(_arg0); (void)(_arg1); (void)(_arg2)
#define LOG_INTERNAL_4(_type, _str, _arg0, _arg1, _arg2, _arg3) \
(void)(_type); (void)(_str); (void)(_arg0); (void)(_arg1); (void)(_arg2); (void)(_arg3)
#define LOG_INTERNAL_5(_type, _str, _arg0, _arg1, _arg2, _arg3, _arg4) \
(void)(_type); (void)(_str); (void)(_arg0); (void)(_arg1); (void)(_arg2); (void)(_arg3); (void)(_arg4)
#define LOG_INTERNAL_6(_type, _str, _arg0, _arg1, _arg2, _arg3, _arg4, _arg5) \
(void)(_type); (void)(_str); (void)(_arg0); (void)(_arg1); (void)(_arg2); (void)(_arg3); (void)(_arg4); (void)(_arg5)
#endif //NRF_LOG_ENABLED
目标函数
最终调用的就是nrf_log_frontend_std_x
函数,x
代表不同入参,这些函数也在nrf_log_intternal.h
中有定义
#define HEADER_SIZE (sizeof(nrf_log_header_t)/sizeof(uint32_t) - \
(NRF_LOG_USES_TIMESTAMP ? 0 : 1))
/**
* @brief A function for logging raw string.
*
* @param severity_mid Severity.
* @param p_str A pointer to a string.
*/
void nrf_log_frontend_std_0(uint32_t severity_mid, char const * const p_str);
/**
* @brief A function for logging a formatted string with one argument.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0 An argument.
*/
void nrf_log_frontend_std_1(uint32_t severity_mid,
char const * const p_str,
uint32_t val0);
/**
* @brief A function for logging a formatted string with 2 arguments.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0, val1 Arguments for formatting string.
*/
void nrf_log_frontend_std_2(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1);
/**
* @brief A function for logging a formatted string with 3 arguments.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0, val1, val2 Arguments for formatting string.
*/
void nrf_log_frontend_std_3(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1,
uint32_t val2);
/**
* @brief A function for logging a formatted string with 4 arguments.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0, val1, val2, val3 Arguments for formatting string.
*/
void nrf_log_frontend_std_4(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1,
uint32_t val2,
uint32_t val3);
/**
* @brief A function for logging a formatted string with 5 arguments.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0, val1, val2, val3, val4 Arguments for formatting string.
*/
void nrf_log_frontend_std_5(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1,
uint32_t val2,
uint32_t val3,
uint32_t val4);
/**
* @brief A function for logging a formatted string with 6 arguments.
*
* @param severity_mid Severity.
* @param p_str A pointer to a formatted string.
* @param val0, val1, val2, val3, val4, val5 Arguments for formatting string.
*/
void nrf_log_frontend_std_6(uint32_t severity_mid,
char const * const p_str,
uint32_t val0,
uint32_t val1,
uint32_t val2,
uint32_t val3,
uint32_t val4,
uint32_t val5);
/**
* @brief A function for logging raw data.
*
* @param severity_mid Severity.
* @param p_str A pointer to a string which is prefixing the data.
* @param p_data A pointer to data to be dumped.
* @param length Length of data (in bytes).
*
*/
void nrf_log_frontend_hexdump(uint32_t severity_mid,
const void * const p_data,
uint16_t length);
/**
* @brief A function for reading a byte from log backend.
*
* @return Byte.
*/
uint8_t nrf_log_getchar(void);
#endif // NRF_LOG_INTERNAL_H__
也就是最终调用的函数
疑问
不过存在几个没有搞明白的问题:
nrf_log_frontend_std_x
的函数实现在哪里,怎么定义到了jlink输出?LOG_INTERNAL_X
到LOG_INTERNAL_X
时,type
也作为可变参数了吗?NUM_VA_ARGS_LESS_1
具体原理是什么?
烦请各位了解的给予解答.
另外在可变参数宏中也会用到**##**,他的具体作用为当可变参数被忽略或为空,‘ ## ’操作将使预处理器( preprocessor )去除掉它前面的那个逗号,防止编译报错,比如:
define mylog(format, ...) printf(format, ## __VA_ARGS__)
mylog("aaa")