常用的预处理命令如下:
l #define 定义一个预处理宏
l #undef 取消宏的定义
l #include 包含文件命令
l #include_next 与#include相似, 但它有着特殊的用途
l #if 编译预处理中的条件命令, 相当于C语法中的if语句
l #ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句
l #ifndef 与#ifdef相反, 判断某个宏是否未被定义
l #elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if
l #else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else
l #endif #if, #ifdef, #ifndef这些条件命令的结束标志.
l #line 标志该语句所在的行号
l # 将宏参数替代为以参数值为内容的字符窜常量
l ## 将两个相邻的标记(token)连接为一个单独的标记
常用的预处理命令中编程技巧:
1)宏定义语句中可以利用"/"来换行.
e.g.
# define ONE 1 /* ONE == 1 */
等价于: #define ONE 1
#define err(flag, msg) if(flag) /
printf(msg)
等价于: #define err(flag, msg) if(flag) printf(msg)
2)函数宏之后的参数要用括号括起来
e.g.
#define mul(x,y) ((x)*(y))
看看下面这个例子:
e.g.
#define mul(x,y) x*y
"mul(1, 2+2);" 将被扩展为: 1*2 + 2
同样, 整个标记串也应该用括号引用起来:
e.g.
#define mul(x,y) (x)*(y)
sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0
3)函数宏的参数问题
e.g.
#define insert(stmt) stmt
insert ( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .
insert ( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.
insert ((a=1, b=2;)) 可解决上述问题.
4)do-while(0)语句
#define swap(x,y) /
do { unsigned long _temp=x; x=y; y=_tmp} while (0)
swap(1,2); 将被替换为: do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);
Linux内核源代码中对这种do-while(0)语句有这广泛的应用.
5) 宏可以被多次定义, 前提是这些定义必须是相同的.
这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab键
e.g.
#define NULL 0
#define NULL /* null pointer */ 0
上面的重定义是相同的, 但下面的重定义不同:
#define fun(x) x+1
#define fun(x) x + 1 或: #define fun(y) y+1
如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.
6) 在gcc中, 可在命令行中指定对象宏的定义:
e.g.
$ gcc -Wall -DMAX=100 -o tmp tmp.c //-D是前缀
相当于在tmp.c中添加" #define MAX 100".
那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?
---若-DMAX=1, 则正确编译.
---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.
注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1
7) #define所定义的宏的作用域
宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别
e.g.
#define TWO 2 // sum = ONE + TWO /* sum = 1 + 2 */
char c[] = "TWO" /*那么 c[] = "TWO",不是等于 "2"! */
8) 使用#if可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.
e.g.
#if 0
{
一大段代码;
}
#endif
9)如果常量表达式为一个未定义的宏, 那么它的值被视为0.
#if MACRO_NON_DEFINED //相当于 #if 0
在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用#ifdef或#ifndef.
10)#ifdef或#ifndef经常用于避免头文件的重复引用:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
代表如果还未定义__FILE_H__,就执行下面两行代码,也就宏定义__FILE_H__引用头文件。
11)defined(name):它有返回值,若宏被定义,则返回1, 否则返回0.
它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. 但defined用于在一条判断语句中声明多个判别条件:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
12)我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.
比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.
13)下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:
__LINE__ 当前语句所在的行号, 以10进制整数标注.
__FILE__ 当前源文件的文件名, 以字符串常量标注.
__DATE__ 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.
__TIME__ 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.
__STDC__ 如果当前编译器符合ISO标准, 那么该宏的值为1
__STDC_VERSION__ 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.
__STDC_HOSTED__ 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.
gcc定义的预定义宏:
__OPTMIZE__ 如果编译过程中使用了优化, 那么该宏被定义为1.
__OPTMIZE_SIZE__ 同上, 但仅在优化是针对代码大小而非速度时才被定义为1.
__VERSION__ 显示所用gcc的版本号.
要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null
14) #, ##的使用
#用于在宏扩展之后将宏定义参数转换为以此参数为内容的字符串常量.
e.g.
#define TEST(a,b) printf( #a "<" #b "=%d/n", (a)<(b));
那么调用TEST(10,9)的效果是printf( “10<9 =%d/n", (10)<(9));
注意: #只针对紧随其后的token有效!
##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.
e.g.
#define TYPE(type, n) type## n
之后调用:
TYPE(int, a) = 1;
TYPE(long, b) = 1999;
将被替换为:
int a = 1;
long b = 1999;