码迷,mamicode.com
首页 > 其他好文 > 详细

C宏定义中的连接符"##"和字符串化操作符"# "及变参宏"..."

时间:2018-01-30 00:27:30      阅读:353      评论:0      收藏:0      [点我收藏+]

标签:运行   div   调用   define   archive   else   gnu   取值   例子   

C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。

1、宏定义中字符串化操作符#:

#的功能是将其后面的宏参数进行字符串化操作,意思就是对它所应用的宏变量通过替换后在其左右各加上一个双引号。

例如: 

#define WARN_IF(EXPR)do {    if (EXPR)        fprintf(stderr, "Warning: " #EXPR "\n");} while(0)

上面代码中的反斜线\主要用来转译换行符,即屏蔽换行符。

那么如下的代码调用:

WARN_IF(divider == 0);

将被替换为:

do {    if (divider == 0)        fprintf(stderr, "Warning: " "divider == 0" "\n");} while(0);

运行结果:

Warning: divider == 0
请按任意键继续. . .

注意能够字符串化操作的必须是宏参数,不是随随便便的某个子串(token)都行的。 

2、宏定义中的连接符##:

连接符##用来将两个token连接为一个token,但它不可以位于第一个token之前or最后一个token之后。注意这里连接的对象只要是token就行,而不一定是宏参数,但是##又必须位于宏定义中才有效,因其为编译期概念(比较绕)。

#define LINK_MULTIPLE(a, b, c, d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name, company, position, salary);
/* 
 * 上面的代码将被替换为
 * typedef struct _record_type name_company_position_salary;
 */

又如下面的例子:

#define PARSER(N) printf("token" #N " = %d\n", token##N)
int token64 = 64;

如下调用宏:

PARSER(64);

将被解析为:

printf("token" "64" " = %d\n", token64);

3、变参宏...的使用

...在C宏中称为Variadic Macro,也就是变参宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
// 或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏 中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出 现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:

myprintf(templt,);

的形式。这时的替换过程为:

myprintf("Error!/n",);
替换为:
fprintf(stderr,"Error!/n",);

这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

myprintf(templt);

而它将会被通过替换变成:

fprintf(stderr,"Error!/n",);

很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

myprintf(templt);
被转化为:
fprintf(stderr,templt);

这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

4、消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

MY_MACRO(x);

但是如果是下面的情况:

#define MY_MACRO(x) {/* line 1 */   /* line 2 */   /* line 3 */ }

//...

if (condition())
MY_MACRO(a);
else
{...}

这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

#define MY_MACRO(x) do {/* line 1 */    /* line 2 */    /* line 3 */ } while(0)

这样只要保证总是使用分号,就不会有任何问题。

5、Duplication of Side Effects

这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

#define MIN(X,Y) ((X) > (Y) ? (Y) : (X))
//...
c = MIN(a,foo(b));

这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写MIN(X,Y)这个宏:

#define MIN(X,Y) \
({\
typeof (X) x_ = (X);\
typeof (Y) y_ = (Y);\
(x_ < y_) ? x_ : y_; })

({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

6、参考:

1.http://www.360doc.com/content/09/0325/10/799_2911227.shtml

2.http://blog.csdn.net/faithmy509/archive/2009/02/20/3914026.aspx

C宏定义中的连接符"##"和字符串化操作符"# "及变参宏"..."

标签:运行   div   调用   define   archive   else   gnu   取值   例子   

原文地址:https://www.cnblogs.com/yulinhanhonor/p/8379872.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!