深入 了解 C宏
C 宏很强大,但我们大多只知道它的替换功能,具体细节总是不清楚,现在时候全面了解它了。
测试方式
gcc –E macro.test.c
参考资料
gcc: http://gcc.gnu.org/onlinedocs/cpp/Macros.html
也可以研究一下boost的 MACRO Metaprogram
或看Linux内核的一些宏技巧(比如list定义,once_call, 等等)
宏的细节
形式参数
形参是个有效的 C 标识符, 以逗号和可选的空格分割。
The parameters must be valid C identifiers, separated by commas and optionally whitespace.
实际参数
实参是以逗号和可选的空格分割。这导致了宏的一个缺陷,参数不能是 (a,b) 这样的,boost的foreach宏就受到这个限制。
gcc不受这个限制
The arguments is separated by commas and optionally whitespace.
例如:
#define CALL(f,a) f a
CALL(printf, ( "%d" , 3 ) ) ==> printf ( "%d" , 3 )
Stringified
#和##只在宏定义中有效。
# stringified 把字符# 右边的 宏参数 转换为字符串 "argument"
example:
#define str(a) #a
str(ADD(x)) ==> "ADD(x)"
Pasted
## pasted 对宏进行参数替换后,去除字符##, 这样就可以实现token合并
example:
#define A abc##def
A ==> abcdef
macro body 展开过程
先进行# stringified操作,再对参数进行替换, 最后执行## pasted 操作。
Simple scan 和 Twice scan
object-like宏 和 function-like但没有参数的宏,或macro body 有 #(stringified ) or ##(pasted) 的macro, 只执行一遍扫描(simple scan)。
否则就要执行两遍扫描。
两篇扫描:
prescan: 对参数进行扫描,并对可以展开的参数进行完全的宏展开。
second scan: 用展开后的参数,对宏体进行展开,对展开后的结果 递归进行 完全的宏展开。
simple scan 执行 second scan 一样的过程。
example:
simple sacn:
#define no_param hah
no_param ==> hah
#define UNAME(a) a##__LINE__
UNAME(lidy) ==> lidy__LINE__
#define INC(x) x+1
#define STR(b) #b
STR(INC(x)) ===> "INC(x)"
递归问题
无论是simple scan 或 twice scan的宏展开过程,都不允许对同一宏进行第二次展开。
example:
simple scan:
#define x (4 + y)
#define y (2 * x)
x ==> (4 + y)
==> (4 + (2 * x))
twice scan:
#define a(x) a(x)+1
#define b(x) x+2
b(a(y)) ==>b(a(y)+1)
==>a(y)+1 +2
可变参数的宏
用__VA_ARGS__ 引用可变参数:
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
eprintf("abc:%d", 3) ===> fprintf(stderr,"abc:%d", 3)
eprintf("bad") ===> fprintf(stderr,"abc:%d", ) 出错,参数太少了
使用##__VA_ARGS___ 可以处理0参数的情形
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
eprintf("abc:%d", 3) ===> fprintf(stderr,"abc:%d", 3)
eprintf("bad") ===> fprintf(stderr,"abc:%d")
使用其它名字
#define eprintf(format, args...) fprintf (stderr, format , ##args)
宏不允许重复定义
如果两个宏定义基本一致,是不会报错的。
判断宏一致:4条都要满足
1同是object-or function-like
2 宏体中的token要相同(就是空白分割的token)
3 如果有参数,那么形参要相同
4 有相同的空白处(空白字符数不要求一样和像HTML那样)
相同定义,不报错
#define FOUR (2 + 2)
#define FOUR (2 + 2)
#define FOUR (2 /* two */ + 2)
重复定义错误:
#define FOUR (2 + 2)
#define FOUR ( 2+2 )
#define FOUR ( 2 + 2) //第4条 空白处不对
#define FOUR (2 * 2) //第3条 宏体的TOKEN不同
#define FOUR(score,and,seven,years,ago) (2 + 2) //第一条不符号
宏调用中使用宏指令
Directives Within Macro Arguments
If, within a macro invocation, that macro is redefined,
then the new definition takes effect in time for argument pre-expansion, but the original definition is still used for argument replacement.
宏调用中重定义那个宏,那么新的定义只在参数展开中起作用,外层宏的宏体展开还是使用原先的定义
#define f(x) x x
f (1
#undef f
#define f 2
f)
==>1 2 1 2
f(2 3) ==> 2(2 3)
多行调用一个宏:
Here is an example illustrating this:
#define ignore_second_arg(a,b,c) a; c
ignore_second_arg (foo (),
ignored (),
syntax error);
==> foo (); syntax error
这会导致程序的序号提示错误。
对于宏调用尽量都在一行内完成。
ignore_second_arg (foo (),ignored (), syntax error);
typeof扩展和 embeded statement ({})表达式
减少重复计算,可以使用 gcc的 typeof扩展和 语句表达式
({...}) 对语句进行计算,位于括号中的复合语句的最后一句必需是一个以分号结尾的表达式,它的值将成为这个语句表达式的值。
#define min(X, Y) \
({ typeof (X) x_ = (X); \
typeof (Y) y_ = (Y); \
(x_ < y_) ? x_ : y_; })
简单语句模拟
对于多个语法行的宏定义,建议使用do {...} while (0) 来包裹, 这可以把宏调用当成是一个简单的语句
#define SKIP_SPACES(p, limit) \
{ char *lim = (limit); \
while (p < lim) { \
if (*p++ != ' ') { \
p--; break; }}}
if (*p != 0)
SKIP_SPACES (p, lim);
else ...
将出错。
好的方式是:
#define SKIP_SPACES(p, limit) \
do { char *lim = (limit); \
while (p < lim) { \
if (*p++ != ' ') { \
p--; break; }}} \
while (0)