C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题作了简单总结。c#
在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操做(Stringfication),简单说就是在对它所引用的宏变量 经过替换后在其左右各加上一个双引号。好比下面代码中的宏:数组
#define WARN_IF(EXP) do{ \ if (EXP) \ fprintf(stderr, "Warning: " #EXP "/n"); }\ while(0)
那么实际使用中会出现下面所示的替换过程:ide
WARN_IF (divider == 0); 被替换为 do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "/n"); } while(0);
这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。
而##被称为链接符(concatenator),用来将两个Token链接为一个Token。注意这里链接的对象是Token就行,而不必定是宏的变量。好比你要作一个菜单项命令名和函数指针组成的结构体的数组,而且但愿在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就很是实用:函数
struct command { char * name; void (*function) (void); }; #define COMMAND(NAME) { NAME, NAME ## _command } // 而后你就用一些预先定义好的命令来方便的初始化 //一个command结构的数组了: struct command commands[] = { COMMAND(quit), COMMAND(help), ... }
COMMAND宏在这里充当一个代码生成器的做用,这样能够在必定程度上减小代码密度,间接地也能够减小不留心所形成的错误。咱们还能够n个##符号链接 n+1个Token,这个特性也是#符号所不具有的。好比:ui
#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;
使用...在C宏中称为Variadic Macro,也就是变参宏。好比:lua
#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",);spa
这是一个语法错误,不能正常编译。这个问题通常有两个解决方法。首先,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合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。翻译
宏的定义不必定要有完整的、配对的括号,可是为了不出错而且提升可读性,最好避免这样使用。指针
由操做符优先级引发的问题-Operator Precedence Problemcode
因为宏只是简单的替换,宏的参数若是是复合结构,那么经过替换以后可能因为各个参数之间的操做符优先级高于单个参数内部各部分之间相互做用的操做符优先级,若是咱们不用括号保护各个宏参数,可能会产生预想不到的情形。好比:
#define ceil_div(x, y) (x + y - 1) / y
那么
a = ceil_div( b & c, sizeof(int) );
将被转化为:
a = ( b & c + sizeof(int) - 1) / sizeof(int);
// 因为+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
这显然不是调用者的初衷。为了不这种状况发生,应当多写几个括号:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))
消除多余的分号-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)
这样只要保证老是使用分号,就不会有任何问题。
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)。