C语言-宏扩展

在嵌入式软件,对打印级别控制非常有用, 如何实现对文件级进行控制呢?

通过宏扩展来进行高效灵活的控制。[1]

实现方法

  1. 创建一个公共头文件log.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义3个打印级别
#define LEVEL_NONE 0
#define LEVEL_ERROR 1
#define LEVEL_INFO 2

// 如果有对文件定义则用文件自己定义的级别,否则用默认级别
#ifdef THIS_FILE_LEVEL
#define LEVEL THIS_FILE_LEVEL() //注意这里需要函数调用
#else
#define LEVEL LEVEL_NONE
#endif

// 通过级别进行控制,定义不同级别的打印的函数
#if LEVEL >= LEVEL_INFO
#define log_info(arg...) print(arg)
#else
#define log_info(arg...)
#endif
#if LEVEL >= LEVEL_ERROR
#define log_error(arg...) print(arg)
#else
#define log_error(arg...)
#endif
  1. 在c文件中最开头定义该文件级别
1
2
3
4
5
6
7
#define THIS_FILE_LEVEL() LEVEL_INFO //注意需要在include log.h 前定义级别
#include "log.h"

void test()
{
log_info("Hello test");
}

通过这个方法,在不同的C文件开头定义不同的THIS_FILE_LEVEL()函数,就可以控制该文件的打印级 别了。

重点解释一下

这里为什么要将THIS_FILE_LEVEL定义为宏函数?如果定义为普通宏会怎么样?

定义为宏函数是为了延迟扩展,如果定义为普通函数,则在define时就完成扩展了,这时LEVEL_INFO还 没定义,就达不到想要的控制效果了。

品,细品。。。

扩展思考

通常情况下,一个文件属于一个Module,但一个Module可以有多个文件。如何通过Module级来进行控制 打印级别呢?

可以强制增加一个log_module.h来控制,在log.h开头包含该文件。考虑采用打印级别的优先级 THIS_FILE_LEVEL > THIS_MODULE_LEVEL

1
2
3
4
5
6
7
8
9
10
// log_module.h
#ifdef THIS_FILE_MODULE
#define MODULE THIS_FILE_MODULE()
#endif

#if defined A_MODULE && MODULE == A_MODULE
#define THIS_MODULE_LEVEL() LEVEL_ERROR
#elif defined B_MODULE && MODULE == B_MODULE
#define THIS_MODULE_LEVEL() LEVEL_INFO
#endif
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
// log.h
#include "log_module.h"

// 定义3个打印级别
#define LEVEL_NONE 0
#define LEVEL_ERROR 1
#define LEVEL_INFO 2

// 如果有对文件定义则用文件自己定义的级别,否则用默认级别
#ifdef THIS_FILE_LEVEL
#define LEVEL THIS_FILE_LEVEL() //注意这里需要函数调用
#elif defined THIS_MODULE_LEVEL
#define LEVEL THIS_MODULE_LEVEL()
#else
#define LEVEL LEVEL_NONE
#endif

// 通过级别进行控制,定义不同级别的打印的函数
#if LEVEL >= LEVEL_INFO
#define log_info(arg...) print(arg)
#else
#define log_info(arg...)
#endif
#if LEVEL >= LEVEL_ERROR
#define log_error(arg...) print(arg)
#else
#define log_error(arg...)
#endif
1
2
3
4
5
6
// test.c
#define THIS_FILE_MODULE() A_MODULE
#include "log.h"

...

参数的条件控制

有时候需要判断参数是否为空来决定是否要拼接打印。比如如下需求:

1
#define EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(stat, msg...)

先执行语句stat, 当有msg需要输出时,拼接函数名同时输出msg,否则不输出任何信息。该实现 要求msg表达式不能以字符(开头, 一般也不会。参考 [2]

实现判断参数是否为空

当宏_IS_ARGS_EMPTY的参数args为空时,则扩展为1, 否则编译报错:

1
2
#define _VALID_MACRO_FUNC(...) 1
define _IS_ARGS_EMPTY(args...) _VALID_MACRO_FUNC##args()

实现判断参数是否为空或以括号开头

在c语言中括号比较特殊,可以利用这个特性实现判断参数是否为空或以括号开头,如果是则扩展为1, 否则扩展为0. 如下IS_ARGS_EMPTY_OR_BWITH_BRACKET

1
2
3
4
5
6
7
8
9
10
#define _CONCAT2(a, b...) a##b
#define CONCAT(a, b...) _CONCAT2(a, b)
#define _GET_FIRST_MACRO_ARG(arg0, args...) arg0
#define _VALID_MACRO_FUNC_1 1,
#define _VALID_MACRO_FUNC__VALID_MACRO_FUNC 0,
#define _IS_ARGS_EMPTY_OR_BWITH_BRACKET_EXPAND(args...) \
_GET_FIRST_MACRO_ARG(args)
#define IS_ARGS_EMPTY_OR_BWITH_BRACKET(args...) \
_IS_ARGS_EMPTY_OR_BWITH_BRACKET_EXPAND( \
CONCAT(_VALID_MACRO_FUNC_, _VALID_MACRO_FUNC args()))

注, CONCAT_CONCAT2, IS_ARGS_EMPTY_OR_BWITH_BRACKET_IS_ARGS_EMPTY_OR_BWITH_BRACKET_EXPAND 是利用宏的扩展机制而需要做的拆分。

实现Log控制

1
2
3
4
5
6
7
8
#define _IS_ARGS_EMPTY_1(args...) _IS_ARGS_EMPTY(args)
#define _IS_ARGS_EMPTY_0(args...) 0
#define IS_ARGS_EMPTY(args...) \
CONCAT(_IS_ARGS_EMPTY_, IS_ARGS_EMPTY_OR_BWITH_BRACKET(args))(args)
#define _OUTPUT_LOG_2(arg0, args...) printf(arg0 "\n", ##args)
#define _OUTPUT_LOG_0(args...) _OUTPUT_LOG_2("[log]" args)
#define _OUTPUT_LOG_1(args...)
#define OUTPUT_LOG(args...) CONCAT(_OUTPUT_LOG_, IS_ARGS_EMPTY(args))(args)

此时,通过OUTPUT_LOG就可以实现有Log时,加上头和尾后输出,无log时,不做任何动作。

实现命令和log结合

1
2
3
4
5
6
#define EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(stat, msg...)                  \
do { \
stat; \
OUTPUT_LOG(msg); \
} while (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
#include <stdio.h>

#define _VALID_MACRO_FUNC(...) 1
#define _IS_ARGS_EMPTY(args...) _VALID_MACRO_FUNC##args()

#define _CONCAT2(a, b...) a##b
#define CONCAT(a, b...) _CONCAT2(a, b)
#define _GET_FIRST_MACRO_ARG(arg0, args...) arg0
#define _VALID_MACRO_FUNC_1 1,
#define _VALID_MACRO_FUNC__VALID_MACRO_FUNC 0,
#define _IS_ARGS_EMPTY_OR_BWITH_BRACKET_EXPAND(args...) \
_GET_FIRST_MACRO_ARG(args)
#define IS_ARGS_EMPTY_OR_BWITH_BRACKET(args...) \
_IS_ARGS_EMPTY_OR_BWITH_BRACKET_EXPAND( \
CONCAT(_VALID_MACRO_FUNC_, _VALID_MACRO_FUNC args()))

#define _IS_ARGS_EMPTY_1(args...) _IS_ARGS_EMPTY(args)
#define _IS_ARGS_EMPTY_0(args...) 0
#define IS_ARGS_EMPTY(args...) \
CONCAT(_IS_ARGS_EMPTY_, IS_ARGS_EMPTY_OR_BWITH_BRACKET(args))(args)
#define _OUTPUT_LOG_2(arg0, args...) printf(arg0 "\n", ##args)
#define _OUTPUT_LOG_0(args...) _OUTPUT_LOG_2("[log]" args)
#define _OUTPUT_LOG_1(args...)
#define OUTPUT_LOG(args...) CONCAT(_OUTPUT_LOG_, IS_ARGS_EMPTY(args))(args)

#define EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(stat, msg...) \
do { \
stat; \
OUTPUT_LOG(msg); \
} while (0)


int main(void) {
int i = 0;
EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(i++, "%d", i); // 输出:[log]1
EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(i++, "hello"); // 输出:[log]hello
EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(i++, "%d", i); // 输出:[log]3
EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(i++); // 无输出
EXECUTE_AND_PRINT_WITH_FUNC_IF_HAVE_MSG(i++, "%d", i); // 输出:[log]5
return 0;
}

  1. https://gcc.gnu.org/onlinedocs/gcc-3.0.1/cpp_toc.html#SEC_Contents ↩︎

  2. https://wiki.disenone.site/cpp-C和Cpp宏编程解析/#__va_opt__ ↩︎