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

By .