标签:
第一部分:宏基础
宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见。基本用法如下:
1. 标识符别名
#define BUFFER_SIZE 1024
预处理阶段,
foo = (char *) malloc (BUFFER_SIZE); //被替换成 foo = (char *) malloc (1024);
宏体换行需要在行末加反斜杠 \
#define NUMBERS 1, 2, 3
预处理阶段
int x[] = {NUMBERS}; //被扩展成 int x[] = {1, 2, 3};
2. 宏函数
宏名之后带括号的宏被认为是宏函数。用法和普通函数一样,只不过在预处理阶段,宏函数会被展开。优点是没有普通函数保存寄存器和参数传递的开销,展开后的代码有利于CPU cache的利用和指令预测,速度快。缺点是可执行代码体积大。
#define min(X, Y) ((X) < (Y) ? (X) : (Y)) y = min(1,2); //会被扩展成 y = ((1) < (2) ? (1) : (2));
第二部分:宏的特殊用法
1.字符串化
在宏体中,如果宏参数前加个 #,那么宏体展开的时候,宏参数会被扩展成字符串的形式。如:
#define WARN_IF(EXP) do { if (EXP) fprintf(stderr, "Warning: " #EXP "\n"); } while (0)
WARN_IF (x == 0); 会被扩展成:
#define WARN_IF(EXP) do { if (EXP) fprintf(stderr, "Warning: " "x == 0" "\n"); } while (0)
这种用法可以用在assert中,如果断言失败,可以将失败的语句输出到反馈信息中
2. 连接
在宏体中,如果宏体所在标识符中有##,那么在宏体展开的时候,宏参数会被直接替换到标识符中。如:
#define COMMAND(NAME) {#NAME, NAME ## _command} struct command { char *name; void (*function) (void); };
在宏扩展的时候
struct command commands[] = { COMMAND (quit), COMMAND (help), ... }; //会被扩展成: struct command commands[] = { {"quit", quit_command}, {"help", help_command}, ... };
宏的高级用法及常见的几个坑
1.语法问题
由于是纯文本替换,C预处理器不对宏体做任何语法检查,像缺个括号、少个分号什么的预处理器是不管的。这里要格外小心,由此可能引出各种奇葩的问题,一下很难找到根源。
2.算符优先级问题
不仅宏体是纯文本替换,宏参数也是纯文本替换。有以下一段简单的宏,实现乘法:
#define MULTIPLY(x, y) x*y
MULTIPLY(1,2)没问题,会正常展开成 1*2。有问题的是这种表达式MULTIPLY(1+2, 3),展开后成了 1+2 *3,显然优先级错了。在宏体中,给引用的参数加个括号就能避免这问题。
#define MULTIPLY(x, y) (x) * (y)
MULTIPLY(1+2, 3)就会被展开成(1+2)*(3),优先级正常了。其实这个问题和下面说到的某些问题都属于由于纯文本替换而导致的语义破坏问题,要格外小心。
3. 分号吞噬问题
有如下宏定义:
标签:
原文地址:http://my.oschina.net/lucusguo/blog/508860