NRF52832的日志输出解析( C中可变参数宏使用)

可变参数宏

问题起源

在阅读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_2LOG_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__

也就是最终调用的函数

疑问

不过存在几个没有搞明白的问题:

  1. nrf_log_frontend_std_x的函数实现在哪里,怎么定义到了jlink输出?
  2. LOG_INTERNAL_XLOG_INTERNAL_X时,type也作为可变参数了吗?
  3. NUM_VA_ARGS_LESS_1具体原理是什么?

烦请各位了解的给予解答.

另外在可变参数宏中也会用到**##**,他的具体作用为当可变参数被忽略或为空,‘ ## ’操作将使预处理器( preprocessor )去除掉它前面的那个逗号,防止编译报错,比如:

define mylog(format, ...) printf(format, ## __VA_ARGS__)
mylog("aaa")
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值