深入 了解 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)

posted @ 2010-11-20 03:23  napoleon_liu  阅读(10367)  评论(1编辑  收藏  举报