C语言宏魔法

虽然题目起的好像很厉害的样子,但实际上都是一些既没有技术含量有没有实际意义的东西,很无聊。

一开始,只是想一厢情愿的优化一下一个switch块,它大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
f(int a1, int a2, int a3) {
switch (num) {
case SYS_f1:
return sys_f1((size_t)a1);
case SYS_f2:
return sys_f2();
...
case SYS_f14:
sys_f14((size_t)a1, (char)a2);
return 0;
}
}

不同的条件会调用参数不同的函数,且调用时需要对参数进行类型转换,如果此函数有返回值直接返回,否则返回0。

当然如果num=14,则程序大概需要判断13次?而且之后情况数量也会扩展,所以感觉可以优化掉这个switch,最好是O(1)的。

怎么办呢?用一个函数指针数组!将所有函数的低脂肪倒数组里,到时候直接以num作为数组下标,不就行了吗?但是每个函数的参数和返回值都不同,所以数组的每个函数指针类型必须相同。

所以说,把每个函数都先用另外一个函数包装成统一的参数,然后在函数内进行不同参数类型的转换,大概就变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sys_f1_entry(int a1, int a2, int a3) {
return sys_f1((size_t)a1);
}
int sys_f2_entry(int a1, int a2, int a3) {
sys_f2();
return 0;
}
...
int sys_f14_entry(int a1, int a2, int a3) {
return sys_f14((size_t)a1, (char)a2);
}

typedef int (*entry_func_t)(int, int, int);
entry_func_t entries[] = {
[SYS_f1]=sys_f1_entry,
[SYS_f2]=sys_f2_entry,
...
[SYS_f14]=sys_f14_entry,
};

f(int num, int a1, int a2, int a3) {
return (entries[num])(a1, a2, a3);
}

当然实际上原来f1~f14这些函数本来是内联展开在f函数里的,但是现在展开在了对应的entry函数里,所以多了一次函数调用开销,但是少了13次比较,感觉好像还可以,但实际上如果你读了CSAPP就会知道,当switch的条件数很多时,编译器会自动生成跳转表以进行O(1)跳转,所以说这样做除了增加了一层函数调用开销拖慢了速度以外并没有用。所以这个前提就是错的。不过为了引入主题,就先错着吧。

不过,感觉手动写对应的entry函数,感觉太麻烦了,有没有什么简单的方法去生成这些代码呢?可以用宏!

然而,C语言的宏实际上并不能获取函数的类型(比如获取每个参数的类型,返回值类型之类的),所以这些信息还是需要我们自己手动写进宏里。所以其实这样做除了少打几个字以外并没有太大的用处,倒不如直接在每次编译前用脚本直接生成代码。

继续正题:首先科普一下C语言可变参数宏的用法,接下来就是要对着这个去做文章,它大概的语法是这样:

1
2
3
4
#define MACRO(FORMAT, ...) printf(FORMAT,__VA_ARGS__)
MACRO("%d %d %d", 1, 2, 3); //FORMAT = "%d %d %d", __VA_ARGS__ = 1, 2, 3
MACRO("%d", 1); //FORMAT = "%d", __VA_ARGS__ = 1
MACRO("hello"); //FORMAT = "hello", __VA_ARGS__为空

...在圆括号里会匹配从上一个参数的逗号开始的所有东西,在宏里通过__VA_ARGS__去访问。

然后看一下我们的需求:本质上,我们是要把

1
type1,type2,type3...

变成

1
(type1)a1,(type2)a2,(type3)a3

但是需要注意的是,因为type的数量不定,所以这些type只能塞进__VA_ARGS__可变参数里,但是这整个可变参数是一体的,所以我们需要寻找一种技术,去分别分割、提取出可变参数里的每一个参数,再进行结合。

然后我发现了这篇文章,在文章末尾有这样一些宏:

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
//A和B是否相等,若相等则展开为后面的第一项,否则展开为后面的第二项
//eg. metamacro_if_eq(0, 0)(true)(false) => true
// metamacro_if_eq(0, 1)(true)(false) => false
#define metamacro_if_eq(A, B) \
metamacro_concat(metamacro_if_eq, A)(B)

#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))

#define metamacro_if_eq0(VALUE) \
metamacro_concat(metamacro_if_eq0_, VALUE)

#define metamacro_if_eq0_1(...) metamacro_expand_

#define metamacro_expand_(...) __VA_ARGS__

//返回参数的个数
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

//返回标号为N的参数(标号从0开始)
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)

#define metamacro_concat(A, B) \
metamacro_concat_(A, B)

#define metamacro_concat_(A, B) A ## B

#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)

#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)

#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)

#define metamacro_head_(FIRST, ...) FIRST

#define metamacro_dec(VAL) \
metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

随便找一个宏,说一下原理,其他类似:
metamacro_at(N,...):首先它会用metamacro_concat宏拼出metamacro_atN宏,然后顺便再把这个宏展开(这种做法很厉害也很重要),并把对应的可变参数(__VA_ARGS__)全都塞进去。metamacro_atN宏前面有从0到N-1个固定参数,会分别占用可变参数中的前N个参数,然后再用metamacro_head宏返回剩下可变参数中的第一个参数,即标号为N的参数。

当然,对应的metamacro_atN宏必须要有,否则就会匹配失败。这里仅仅给出了2和20两种情况。

但是首先我们应该先处理返回值问题,因为必须根据函数是否有返回值来返回不同的值,所以我们大概需要有一个编译期的if判断(constexpr if(x))? metamacro_if_eq宏似乎有用,但是缺少了metamacro_if_eq_0_0这个宏,而且1,0时不会工作,于是随便改了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define metamacro_bool_not_1 0
#define metamacro_bool_not_0 1

//返回布尔值x的否
#define metamacro_bool_not(x)\
metamacro_concat(metamacro_bool_not_,x)

//A和B是否相等,若相等则展开为后面的第一项,否则展开为后面的第二项
//eg. metamacro_if_eq(0, 0)(true)(false) => true
// metamacro_if_eq(0, 1)(true)(false) => false
#define metamacro_if_eq(A, B) \
metamacro_concat(metamacro_if_eq, A)(B)

#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_bool_not(VALUE))

#define metamacro_if_eq0(VALUE) \
metamacro_concat(metamacro_if_eq0_, VALUE)

#define metamacro_if_eq0_1(...) metamacro_expand_
#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_expand_empty_
#define metamacro_expand_empty_(...)
#define metamacro_expand_(...) __VA_ARGS__

gcc -E测试了一下metamacro_if_eq(0,0)(true)(false),会返回true

然后我们就可以写了,仔细观察之前的metamacro_at宏,感觉可以如法炮制。我们可以通过metamacro_argcount获得可变参数宏中的参数个数,然后通过用metamacro_concat拼接的方式展开对应参数个数的宏。再用GEN_SYSCALL_ENTRY拼出函数主体。

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
#define SYSCALL_ARGS(...)\
metamacro_concat(SYSCALL_ARG,metamacro_argcount(__VA_ARGS__))(__VA_ARGS__)

#define SYSCALL_ARG0(...)

#define SYSCALL_ARG1(_0)\
(_0)a1

#define SYSCALL_ARG2(_0,_1)\
(_0)a1,(_1)a2

#define SYSCALL_ARG3(_0,_1,_2)\
(_0)a1,(_1)a2,(_2)a3

#define SYSCALL_ARG4(_0,_1,_2,_3)\
(_0)a1,(_1)a2,(_2)a3,(_3)a4

#define SYSCALL_ARG5(_0,_1,_2,_3,_4)\
(_0)a1,(_1)a2,(_2)a3,(_3)a4,(_4)a5

#define GEN_SYSCALL_ENTRY(FUNC,NORETURN, ...) \
static int32_t \
FUNC##_entry(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)\
{\
metamacro_if_eq(NORETURN,1)\
({ FUNC(SYSCALL_ARGS(__VA_ARGS__)); return 0; })\
({ return FUNC(SYSCALL_ARGS(__VA_ARGS__)); })\
}

//for example:
void f1(int,char);
int f2();
GEN_SYSCALL_ENTRY(f1, 1, int, char);
GEN_SYSCALL_ENTRY(f2, 0);

当然无参数的函数在生成时会报错,原因是因为metamacro_argcount中的__VA_ARGS__即使为空,也会占用一个参数的空间,解决方法是改为##__VA_ARGS__,并且在metamacro_argcount末尾添加一个0。详情看这里

1
2
#define metamacro_argcount(...) \
metamacro_at(20, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 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
#define GEN_SYSCALL_ARRAY(...)\
static entry_func_t syscall_entries[] = {GEN_ARRAY_ITEMS(__VA_ARGS__)};

#define GEN_ARRAY_ITEMS(...)\
metamacro_concat(GEN_ARRAY_ITEM,metamacro_argcount(__VA_ARGS__))(__VA_ARGS__)

#define GEN_ARRAY_ITEM5(_0,_1,_2,_3,_4)\
[SYS_##_0]=sys_##0_entry,\
[SYS_##_1]=sys_##1_entry,\
[SYS_##_2]=sys_##2_entry,\
[SYS_##_3]=sys_##3_entry,\
[SYS_##_4]=sys_##4_entry,
#define GEN_ARRAY_ITEM4(_0,_1,_2,_3)\
[SYS_##_0]=sys_##0_entry,\
[SYS_##_1]=sys_##1_entry,\
[SYS_##_2]=sys_##2_entry,\
[SYS_##_3]=sys_##3_entry,
#define GEN_ARRAY_ITEM3(_0,_1,_2)\
[SYS_##_0]=sys_##0_entry,\
[SYS_##_1]=sys_##1_entry,\
[SYS_##_2]=sys_##2_entry,
#define GEN_ARRAY_ITEM2(_0,_1)\
[SYS_##_0]=sys_##0_entry,\
[SYS_##_1]=sys_##1_entry,
#define GEN_ARRAY_ITEM1(_0)\
[SYS_##_0]=sys_##0_entry,

//for example:
typedef int (*entry_func_t)(int, int, int);
entry_func_t entries[] = {
[SYS_f1]=f1_entry,
[SYS_f2]=f2_entry,
...
[SYS_f14]=f14_entry,
};
// it equal to below
GEN_SYSCALL_ARRAY(f1,\
f2,\
...
f14)

通过简单枚举出参数个数的所有情况,
但是需要生成许多GEN_ARRAY_ITEM*,能不能有什么更简单的写法呢?

看到GEN_ARRAY_ITEM4中就包含着GEN_ARRAY_ITEM3,所以大概可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
 #define GEN_ARRAY_ITEM1(_0)\
[SYS_##_0]=sys_##_0

#define GEN_ARRAY_ITEM2(_0, ...)\
[SYS_##_0]=sys_##_0, metamacro_concat(GEN_ARRAY_ITEM,metamacro_argcount(__VA_ARGS__))(__VA_ARGS__)

#define GEN_ARRAY_ITEM3(_0, ...)\
[SYS_##_0]=sys_##_0, metamacro_concat(GEN_ARRAY_ITEM,metamacro_argcount(__VA_ARGS__))(__VA_ARGS__)

#define GEN_ARRAY_ITEM4(_0, ...)\
[SYS_##_0]=sys_##_0, metamacro_concat(GEN_ARRAY_ITEM,metamacro_argcount(__VA_ARGS__))(__VA_ARGS__)

GEN_ARRAY_ITEM4(a,b,c,d)

如果把宏中参数的个数看做函数的一个状态,那么
GEN_ARRAY_ITEM*每次从可变参数宏里提取出第一个参数_0,之后对它进行处理,然后更新自己的状态,切换到下一个状态。

当然,可以把这个过程看作是把一个list(即__VA_ARGS__)中的每一个元素(参数)进行处理,之后返回处理后的list的过程的话,那么可以抽象成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define GEN_LIST5(ARG_FUNC,_0,...) ARG_FUNC(_0)\
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

#define GEN_LIST4(ARG_FUNC,_0,...) ARG_FUNC(_0)\
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

#define GEN_LIST3(ARG_FUNC,_0,...) ARG_FUNC(_0)\
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

#define GEN_LIST2(ARG_FUNC,_0,...) ARG_FUNC(_0)\
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

#define GEN_LIST1(ARG_FUNC,_0,...) ARG_FUNC(_0)\
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

#define GEN_LIST0(ARG_FUNC,...)

#define GEN_LIST(ARG_FUNC,...) \
metamacro_concat(GEN_LIST,metamacro_argcount(__VA_ARGS__))(ARG_FUNC,__VA_ARGS__)

其中ARG_FUNC可以看作对每一个参数进行处理的过程,这样,任何“将不定的参数逐个进行处理”的宏都可以用GEN_LIST宏重新定义。比如GEN_ARRAY_ITEMS

1
2
3
4
5
#define ITEM(x)\
[SYS_##x]=sys_##x##_entry,\

#define GEN_ARRAY_ITEMS(...)\
GEN_LIST(ITEM,__VA_ARGS__)

这样做就好像是函数式语言里的高阶函数,将函数像对象一样传来传去一样,只不过实际上C语言的宏并不能计算什么东西,所以需要生成大量的宏用于辅助,实际上你去生成这些辅助宏就相当于你手动把计算过程预先提出来,提前帮宏计算好了一样,所以实际上并没有用。

我感觉我最近可能是看SICP看傻了。

最后把所有的结果贴上来。