背景
最近接到一个需求,要实现一个脚本,能提取.h
文件里定义的所有全局变量
的值,这些全局变量都是结构体变量,名字是结构体类型名
加场景
后缀——每个.h
对应的场景都是唯一的,所以.h
内所有变量名的后缀一致。
我的解决方案
我想到的解决方案是,写一个打桩的.c
文件,里面实现一个accessor
函数,根据参数结构体名
和场景名
返回对应的变量值
。结构体名种类固定,可以写死,但场景名无限多,必须动态传入,于是想到使用gcc的-D
选项。该选项可以实现#define
语句的效果,即定义一个宏
,场景就可以通过宏展开来传递了,示例代码如下:
int luma_cinema = 3;
int luma_theater = 4;
#define PASTE(param_name, scene_name) param_name ## _ ## scene_name
int main() {
//return luma_##SCENE; //符号拼接只能在#define语句内出现!
return PASTE(luma, SCENE);
}
脚本通过如下命令行传递场景名:
gcc -DSCENE=cinema test_sharp.c
我的方案失败了
上面的代码编译不过:
test_sharp.c: In function ‘main’:
test_sharp.c:8:18: error: ‘luma_SCENE’ undeclared (first use in this function); did you mean ‘SCENE’?
return PASTE(luma, SCENE);
^
test_sharp.c:4:39: note: in definition of macro ‘PASTE’
#define PASTE(param_name, scene_name) param_name ## _ ## scene_name
^~~~~~~~~~
test_sharp.c:8:18: note: each undeclared identifier is reported only once for each function it appears in
return PASTE(luma, SCENE);
^
test_sharp.c:4:39: note: in definition of macro ‘PASTE’
#define PASTE(param_name, scene_name) param_name ## _ ## scene_name
^~~~~~~~~~
分析
查看cpp命令(C preprocessor)的输出
查看cpp
的输出
cpp -DSCENE=cinema test_sharp.c
发现只执行了符号拼接
,没有执行宏展开
:
# 1 "test_sharp.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test_sharp.c"
int luma_cinema = 3;
int luma_theater = 4;
int main() {
return luma_SCENE;
}
自己的理解
当展开PASTE
宏时,cpp只会执行一次展开,即宏函数的展开,不会再对宏函数的参数进行展开了。
要想先展开宏定义,再展开宏拼接,需要两次展开
,怎么实现两次展开呢?
查看cpp的官方文档
查看官方文档,发现这行话:
看样子就是定义2个宏函数,类似于二级指针的意思。
解决问题
修改我的原始代码:
int luma_cinema = 3;
int luma_theater = 4;
#define _PASTE(param_name, scene_name) param_name ## _ ## scene_name
#define PASTE(param_name, scene_name) _PASTE(param_name, scene_name)
int main() {
//return luma_##SCENE;
return PASTE(luma, SCENE);
}
这次gcc编译通过,运行效果:
$ gcc -DSCENE=cinema test_sharp.c
$ ./a.out
$ echo $?
3
a.out程序退出时返回3,说明符合代码预期。
查看cpp的输出,也符合预期:
# 1 "test_sharp.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test_sharp.c"
int luma_cinema = 3;
int luma_theater = 4;
int main() {
return luma_cinema;
}
后记
昨天这个问题逼得我无奈使用sed
动态替换打桩.c
里的场景名,非常笨拙,并熬夜看了一部分GNU m4
的资料。没想到预处理器本身就支持,以后有时间要好好研究预处理器。