C 语言中宏的正常用法
Table of Contents
本文介绍一些 C/C++ 中宏的正常用法。以及一些你绝对不会想让你家孩子知道的令人惊叹的技术。
1 相关知识
宏是在编译之前由预处理器处理的替换规则, 仅进行字符串替换, 并没有值的感念. 宏有两种风格, 一种和对象类似:
#define identifier replacement-list
这里 identifier 是宏的名字, replacement-list 是一个或多个 tokens 的序列。在后面的程序中所有出现 identifier 的地方都会被展开为 replacement-list 。
另一种是函数风格的宏,它就好比“预处理期的元函数”:
#define identifier(a1, a2, ... an) replacement-list
这里,每一个ai 都代表一个宏形参(parameter)的名字。如果后面的程序中用到了该宏,并给出了适当的实参(argument),那么它将被扩展为 replacement-list ——其中每次出现宏形参的地方都会被替换为用户给出的宏实参。
如果学习过 C 语言,我们通常会像这样使用宏:
int bigarray[MAXN];
这样在预处理期, MAXN会被替换成10001, 也就是说上面的代码在预处理之后变成了
int bigarray[10001];
使用 gcc的 -E 选项可以查看预处理结果, 也有更好的方式, 将在后面介绍.
函数形式的宏比较常用:
#ifndef min # define min(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; }) #endif #ifndef max # define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y; }) #endif #ifndef swap #define swap(x,y) do { \ typeof(x) __tmp = (x); \ (x) = (y); (y) = __tmp; \ } while(0) #endif
swap 宏中do … while(0) 的作用是将几条语句合成一条. 如果没有 do {…} while(0), 下面这句话就会有问题:
if (x > y) swap(x, y);
如果没有 do {…} while (0), 则只有 typeof(x) __tmp = (x); 在 if 的条件为真下执行, 其它会在任何条件下执行.
在GCC环境中,如果对效率有较高的要求, 可能会需要下面这两个宏:
#ifndef likely # define likely(x) __builtin_expect(!!(x), 1) #endif #ifndef unlikely # define unlikely(x) __builtin_expect(!!(x), 0) #endif
likely(x) 告诉编译器 x 为真的概率较大, 编译器据此可以优化跳转预测.
比较常用的宏还有下面这个:
#ifndef container_of /** * container_of - cast a member of a structure out to * the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ # define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
它的用处注释里都已经说清楚了。
2 宏的规则
如果你忘记了, 那么函数形式的宏是这个样子的:
#define identifier(a1, a2, ... an) replacement-list
- identifier(…) 替换为 replacement-list, 其中的实參将按照宏的定义放置到对应位置.
- 如果实參是宏, 则先展开 identifier 的实參, 再展开当前宏定义的 identifier(…), 除非遇到规则3.
- replacement-list 中形如 \# a1 的被替换为字符串 a1.
- replacement-list 中的 name\#\# a1 将被预处理器连接 namea1, 其中 a1 为实參.
- replacement-list 中出现当前定义的 identifier, 则停止展开.
3 打印宏展开后的样子
仔细观察宏的规则, 很容易理解将宏展开之后的样子转换为字符串以打印出来的宏:
#define PP_STRINGIZE(text) PP_STRINGIZE_I(text) #define PP_STRINGIZE_I(text) #text
此后就可以使用这个宏来观察一个宏展开之后的样子了:
puts(PP_STRINGIZE(min(1, 2)));
4 使用宏进行逻辑运算
C语言中, 我们将0视为true, 将非0视为false。但大多数时候我们已经将true定义为1, 将false定义为0。要想把数值转换为true/false, 需要费一些力气。下面的宏做到了0到256的整数数值转换为true/false。因为很多代码是重复的, 为了节省篇幅, 不在这里全部列出。实际上, 我也没有一行一行将从键盘输入, 而是用编辑器的宏录制功能写了大部分代码.
#define PP_BOOL(x) PP_BOOL_I(x) #define PP_BOOL_I(x) PP_BOOL_ ## x #define PP_BOOL_0 0 #define PP_BOOL_1 1 #define PP_BOOL_2 1 #define PP_BOOL_3 1 ... #define PP_BOOL_256 1
使用下面的代码来验证一下这个宏. 注意宏是预处理器处理的, 不要试图给他传递一个表示某个值的变量, 宏只能进行简单字符串替换.
puts(PP_STRINGIZE(PP_BOOL(100))); puts(PP_STRINGIZE(PP_BOOL(0)));
在 PP_BOOL 的基础上, 定义一些逻辑运算变得非常简单:
/* bitand */ #define PP_BITAND(x, y) PP_BITAND_I(x, y) #define PP_BITAND_I(x, y) PP_BITAND_ ## x ## y #define PP_BITAND_00 0 #define PP_BITAND_01 0 #define PP_BITAND_10 0 #define PP_BITAND_11 1 /* bitnor */ #define PP_BITNOR(x, y) PP_BITNOR_I(x, y) #define PP_BITNOR_I(x, y) PP_BITNOR_ ## x ## y #define PP_BITNOR_00 1 #define PP_BITNOR_01 0 #define PP_BITNOR_10 0 #define PP_BITNOR_11 0 /* bitor */ #define PP_BITOR(x, y) PP_BITOR_I(x, y) #define PP_BITOR_I(x, y) PP_BITOR_ ## x ## y #define PP_BITOR_00 0 #define PP_BITOR_01 1 #define PP_BITOR_10 1 #define PP_BITOR_11 1 /* bitxor */ #define PP_BITXOR(x, y) PP_BITXOR_I(x, y) #define PP_BITXOR_I(x, y) PP_BITXOR_ ## x ## y #define PP_BITXOR_00 0 #define PP_BITXOR_01 1 #define PP_BITXOR_10 1 #define PP_BITXOR_11 0
传给上述宏的参数可能是数值而非简单的0/1, 需要使用PP_BOOL 转换一遍:
#define PP_COMPL(x) PP_COMPL_I(x) #define PP_COMPL_I(x) PP_COMPL_ ## x #define PP_COMPL_0 1 #define PP_COMPL_1 0 #define PP_AND(p, q) PP_BITAND(PP_BOOL(p), PP_BOOL(q)) #define PP_NOR(p, q) PP_BITNOR(PP_BOOL(p), PP_BOOL(q)) #define PP_NOR(p, q) PP_BITNOR(PP_BOOL(p), PP_BOOL(q)) #define PP_NOT(x) PP_COMPL(PP_BOOL(x)) #define PP_OR(p, q) PP_BITOR(PP_BOOL(p), PP_BOOL(q)) #define PP_XOR(p, q) PP_BITXOR(PP_BOOL(p), PP_BOOL(q))
5 数据结构
tuple就是某些语言中的元组, 宏也可以实现这种结构.
首先需要一个宏, 将两个宏展开后拼接:
#define PP_CAT(a, b) PP_CAT_I(a, b) #define PP_CAT_I(a, b) a ## b
下面定义 tuple 展开. 省略了中间很多相似内容。
#define PP_REM(...) __VA_ARGS__ #define PP_TUPLE_REM(size) PP_TUPLE_REM_I(size) #define PP_TUPLE_REM_I(size) PP_TUPLE_REM_ ## size #define PP_TUPLE_REM_0() #define PP_TUPLE_REM_1(e0) e0 #define PP_TUPLE_REM_2(e0, e1) e0, e1 #define PP_TUPLE_REM_3(e0, e1, e2) e0, e1, e2 #define PP_TUPLE_REM_4(e0, e1, e2, e3) e0, e1, e2, e3 #define PP_TUPLE_REM_5(e0, e1, e2, e3, e4) e0, e1, e2, e3, e4 ... #defieni PP_TUPLE_REM_64 ...
定义另一个宏, 可以吃掉元组头n个元素。
# define PP_EAT(...) #define PP_TUPLE_EAT(size) PP_EAT #define PP_TUPLE_EAT_1(e0) #define PP_TUPLE_EAT_2(e0, e1) #define PP_TUPLE_EAT_3(e0, e1, e2) #define PP_TUPLE_EAT_4(e0, e1, e2, e3) #define PP_TUPLE_EAT_5(e0, e1, e2, e3, e4) ...
相反的, 要插入一个元素, 也非常简单:
#define PP_TUPLE_INSERT(tuple, i, elem) \ PP_ARRAY_TO_TUPLE(PP_ARRAY_INSERT(PP_TUPLE_TO_ARRAY(tuple), i, elem)) \ #define PP_TUPLE_INSERT_D(d, tuple, i, elem) \ PP_ARRAY_TO_TUPLE(PP_ARRAY_INSERT_D(d, PP_TUPLE_TO_ARRAY(tuple), i, elem)) \
呃, 好像不是非常明显, 你也看到了, 这个宏引用了另一个数据结构ARRAY。实际上有了 tuple 之后可以定义很多数据结构, 比如 array 和 list,甚至 tree。 实现这些数据结构需要很多罗嗦重复的工作。有兴趣的读者可以阅读 boost 的 preprocessor。
6 控制逻辑
先实现if语句:
#define PP_IIF(bit, t, f) PP_IIF_I(bit, t, f) #define PP_IIF_I(bit, t, f) PP_IIF_ ## bit(t, f) #define PP_IIF_0(t, f) f #define PP_IIF_1(t, f) t #define PP_IF(cond, t, f) PP_IIF(PP_BOOL(cond), t, f) #define PP_EXPR_IIF(bit, expr) PP_EXPR_IIF_I(bit, expr) #define PP_EXPR_IIF_I(bit, expr) PP_EXPR_IIF_ ## bit(expr) #define PP_EXPR_IIF_0(expr) #define PP_EXPR_IIF_1(expr) expr #define PP_EXPR_IF(cond, expr) PP_EXPR_IIF(PP_BOOL(cond), expr)
while循环可能看起来更有用, 这里PP_AUTO_REC总是展开成1.
#define PP_WHILE PP_CAT(PP_WHILE_, PP_AUTO_REC(PP_WHILE_P, 256)) #define PP_WHILE_P(n) PP_BITAND(PP_CAT(PP_WHILE_CHECK_, PP_WHILE_ ## n(PP_WHILE_F, PP_NIL, PP_NIL)), PP_CAT(PP_LIST_FOLD_LEFT_CHECK_, PP_LIST_FOLD_LEFT_ ## n(PP_NIL, PP_NIL, PP_NIL))) #define PP_WHILE_F(d, _) 0 #define PP_WHILE_257(p, o, s) PP_ERROR(0x0001) #define PP_WHILE_CHECK_PP_NIL 1 #define PP_WHILE_CHECK_PP_WHILE_1(p, o, s) 0 #define PP_WHILE_CHECK_PP_WHILE_2(p, o, s) 0 #define PP_WHILE_CHECK_PP_WHILE_3(p, o, s) 0 #define PP_WHILE_CHECK_PP_WHILE_4(p, o, s) 0 #define PP_WHILE_CHECK_PP_WHILE_5(p, o, s) 0 ... #define PP_WHILE_1(p, o, s) PP_WHILE_1_C(PP_BOOL(p(2, s)), p, o, s) #define PP_WHILE_2(p, o, s) PP_WHILE_2_C(PP_BOOL(p(3, s)), p, o, s) #define PP_WHILE_3(p, o, s) PP_WHILE_3_C(PP_BOOL(p(4, s)), p, o, s) #define PP_WHILE_4(p, o, s) PP_WHILE_4_C(PP_BOOL(p(5, s)), p, o, s) #define PP_WHILE_5(p, o, s) PP_WHILE_5_C(PP_BOOL(p(6, s)), p, o, s) ... #define PP_WHILE_1_C(c, p, o, s) PP_IIF(c, PP_WHILE_2, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(2, s)) #define PP_WHILE_2_C(c, p, o, s) PP_IIF(c, PP_WHILE_3, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(3, s)) #define PP_WHILE_3_C(c, p, o, s) PP_IIF(c, PP_WHILE_4, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(4, s)) #define PP_WHILE_4_C(c, p, o, s) PP_IIF(c, PP_WHILE_5, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(5, s)) #define PP_WHILE_5_C(c, p, o, s) PP_IIF(c, PP_WHILE_6, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(6, s)) #define PP_WHILE_6_C(c, p, o, s) PP_IIF(c, PP_WHILE_7, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(7, s)) #define PP_WHILE_7_C(c, p, o, s) PP_IIF(c, PP_WHILE_8, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(8, s)) ...
7 boost/preprocessor
boost 实现了很多方便的宏,读者可以自行阅读。
下面列举几个使用 boost 的代码, 宏替代了很多重复的工作。
#include <boost/preprocessor/arithmetic/add.hpp> #include <boost/preprocessor/control/while.hpp> #include <boost/preprocessor/tuple/elem.hpp> #define PRED(n, state) BOOST_PP_TUPLE_ELEM(2, 1, state) #define OP(d, state) \ OP_D( \ d, \ BOOST_PP_TUPLE_ELEM(2, 0, state), \ BOOST_PP_TUPLE_ELEM(2, 1, state) \ ) \ /**/ #define OP_D(d, res, c) \ ( \ BOOST_PP_ADD_D( \ d, \ res, \ BOOST_PP_DEC(c) \ ), \ BOOST_PP_DEC(c) \ ) \ /**/ #define SUMMATION(n) \ BOOST_PP_TUPLE_ELEM( \ 2, 0, \ BOOST_PP_WHILE(PRED, OP, (n, n)) \ ) \ /**/ SUMMATION(3) // expands to 6 SUMMATION(4) // expands to 10
也可以在每次循环都生成结果:
#include <boost/preprocessor/arithmetic/inc.hpp> #include <boost/preprocessor/comparison/not_equal.hpp> #include <boost/preprocessor/repetition/for.hpp> #include <boost/preprocessor/tuple/elem.hpp> #define PRED(r, state) \ BOOST_PP_NOT_EQUAL( \ BOOST_PP_TUPLE_ELEM(2, 0, state), \ BOOST_PP_INC(BOOST_PP_TUPLE_ELEM(2, 1, state)) \ ) \ /**/ #define OP(r, state) \ ( \ BOOST_PP_INC(BOOST_PP_TUPLE_ELEM(2, 0, state)), \ BOOST_PP_TUPLE_ELEM(2, 1, state) \ ) \ /**/ #define MACRO(r, state) BOOST_PP_TUPLE_ELEM(2, 0, state) BOOST_PP_FOR((5, 10), PRED, OP, MACRO) // expands to 5 6 7 8 9 10