阅读大型C工程代码时,绕不开带参数的宏定义的阅读,所以有必要强化一下这一块的知识。
01-带参数的宏定义最常用的形式
# define S(a,b) a*b
...
...
...
area = S(3,2);
则在编译预处理时area = S(3,2);
被展开为:
area = 3 * 2;
02-带标记分隔符##
的宏定义
为什么要引入标记分隔符##
?
在 C 语言的 预处理器中,对于带参数的宏替换,实际上是以标记为单位来替换的。
比如在下面的宏定义:
# define S(a,b) a*b
在a*b
中,a是一个标记,b也是一个标记,两个标记通过运算符*
进行了划分。
在实际展开时,第一个参数名因为与第一个标记相同,所以去替换第一个标记a;第二个参数名因为与第二个标记相同,所以去替换第二个标记b。
又如:
# define S(aaa,bbb) aaa*bbb
在aaa*bbb
中,aaa是一个标记,bbb也是一个标记,两个标记通过运算符*
进行了划分。
在实际展开时,第一个参数名因为与第一个标记相同,所以去替换第一个标记aaa;第二个参数名因为与第二个标记相同,所以去替换第二个标记bbb。
此时
S(su,wenhao)
会展开为:su*wenhao
但是对于下面这个宏定义:
#define S1(aaa, bbb) aaa*bbbk
就出问题了,因为在aaa*bbbk
中,两个标记分别为aaa和bbbk(两个标记通过运算符*
进行了划分),第一个参数名因为与第一个标记相同,所以去替换第一个标记aaa,第二个参数名因与第二个标记bbbk不相同,所以无法替换,此时:
S(su,wenhao)
的展开结果为:su*bbbk
。
但其实我们这里想得到的结开结果为su*wenhaok
那么办呢?我们就需要在bbb
和k
之间加一个标记分隔符,这个标记分隔符就是##
,即把宏定义写成下面这样就能得到我们想要的结果:
#define S1(aaa, bbb) aaa*bbb##k
##
的用途举例
1. 动态生成变量名
通过宏创建多个变量名:
#define CREATE_VAR(name) int var_##name;
CREATE_VAR(foo) // 生成 int var_foo;
CREATE_VAR(bar) // 生成 int var_bar;
2. 动态生成函数
可以用宏来定义多个函数:
#define CREATE_FUNC(name) void func_##name(void) { printf(#name " function called\n"); }
CREATE_FUNC(init) // 生成 void func_init(void) { printf("init function called\n"); }
CREATE_FUNC(exit) // 生成 void func_exit(void) { printf("exit function called\n"); }
调用时:
func_init(); // 输出:init function called
func_exit(); // 输出:exit function called
我在实际工程中遇到使用##
的例子
在Linux嵌入式驱动开发中,对于设备类的属性的定义用到了带##
的宏。
CLASS_ATTR_WO(led_all_control);
这个宏的定义如下:
#define CLASS_ATTR_WO(_name) struct class_attribute class_attr_##_name = __ATTR_WO(_name)
由于分隔符##
的存在,所以_name
成为一个标记,所以CLASS_ATTR_WO(led_all_control);
会被展开为:
struct class_attribute class_attr_led_all_control = __ATTR_WO(led_all_control)
上面这个展开式的右边其实又是一个带参数的宏定义,这里我就不把它展开了,我们这里重点是强调宏标记分隔符##
的使用。