响水凹

欢迎来到 Guang-Wen Duan (Dennis Duan) 的个人 Wiki

用户工具

站点工具


computer:c:tips:macro_with_parameters

参数宏(Macro with parameters)

括号规则

对于带参数的宏,定义时左括号(parenthesis)必须紧接着宏名,之间不能有空格,否则,从左括号开始的部分都被识别为宏的定义。当运行时,宏名和左括号之间可以有空白。

宏调用时,实参可以包含多层括号,括号内可以有逗号(commas)。大括号(braces)和中括号(brackets)也可以出现在实参里,但他们里面不能包含逗号。

例如定义:

#define insert(stmt) stmt

如下调用是合法的:

        insert( { a = 1; b = 1; } )

但这个就不行:

        insert( { a = 1, b = 1; } )

必须写成这样才行:

        insert( {  (a = 1, b = 1);  } )

有点诡异,但这是语法的要求。

宏的本质

编写带参数的宏需要特别小心,因为宏扩展的本质仅仅是进行文本式的替换(textual substitution),它不是真正的函数。

例如:

#define SQUARE(x) x * x

本意是求平方,但调用:

SQUARE(z + 1)

的扩展结果却是:

z + 1 * z + 1

一个安全的做法是在宏体内使用括号括住每个参数。如果整个宏的形式是一个表达式,那么宏也用括号括住。

上例改造后:

#define SQUARE(x) ((x) * (x))

如果没有用括号括住整个宏,那么下面的调用仍旧可能会出问题:

(short)SQUARE(z + 1)

因为强制转换的优先级更高。

宏的副作用

括号不是万能的,还要注意宏参数可能存在的副作用,毕竟宏不是函数。

例如:

        a = 3;
        b = SQUARE(a++);

导致a++了两次,结果为5,而b的结果则依赖于实现。如果SQUARE是个函数,就不会有这个问题。

do-while模式

对于多语句的情况,一般用do-while结构比较合适。

比如下面的实现:

#define swap(x, y) {unsigned long temp = x; x = y; y = temp;}

实际调用时,很容易在末尾加个分号(semicolon),导致编译出错:

        if ( x > y)
                swap(x, y);
        else
                x = y;

改用do-while实现就好了:

#define swap(x, y) \
        do { unsigned long temp = x; x = y; y = temp; } while (0)

变参宏

C99允许在参数中使用...实现变参宏。所有额外的参数,包含其中的逗号,用标识符__VA_ARGS__代替。

比如:

#define my_printf(...)	fprintf(stderr, __VA_ARGS__)

调用:

        my_printf("x = %d\n", x);

的扩展结果为:

        fprintf(stderr, "x = %d\n", x);

另一个例子:

#define make_em_a_string(...)   #__VA_ARGS__

调用:

        printf("%s\n", make_em_a_string(a, b, c, d));

的扩展结果为:

        printf("%s\n", "a, b, c, d");
computer/c/tips/macro_with_parameters.txt · 最后更改: 2014/11/01 02:02 由 127.0.0.1