C语言预处理机制——学习笔记
一、变量式宏定义(Oject-like Macro)
宏只是进行简单的字符串替换:#define N 20
#define STR "hello, world\n"
二、函数式宏定义(Function-like Macro)
宏可以类似函数一样使用:#define MAX(a, b) ((a)>(b)?(a):(b))
下面是他的几个特点特点:
- 参数没有类型,只是形式上的替换,不过类型检查,传参数时要格外小心
- 调用真正的函数和调用函数式宏定义的代码编译生成的指令不同
- 定义时要注意每个参数要加括号,最外层也要加括号
- 调用函数时先求实参表达式再传给形参,如果实参表达式有side effect,这些side effect只发生一次
- 有可能重复求值,小心定义这类宏
#define sdram_selfrefresh_disable(saved_lpr0) \
do
{ \
at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0); \
at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1); \
}
while (0)
这里的宏用do---while括起来就是为了让这个宏替换的代码总是当作一个整体看待。
三、内联函数(inline function)
内联函数是为了减少因函数调用产生的开销,会在调用函数时进行代码展开,而不是函数调用的方式,弊端是会导致编译文件变大:static inline int rwsem_is_locked(struct rw_semaphore *sem) { return sem->count != 0; }
四、#和##运算符和可变参数
#和##是预处理运算符,在函数式宏定义中,#运算符后面应该跟一个形参,之间用空格或Tab分割,用于创建字符串字面值:#define STR(s) # s STR(hello world)
调用STR宏产生字符串字面值“hello world”。
在宏定义中##运算符把前后两个预处理Token连接成一个预处理Token,并且变量式宏定义中也可以用##运算符:
#define CONCAT(a, b) a##b
CONCAT(con, cat)
预处理之后是concat,比如定义一个宏展开为两个#符号:
#define HASH_HASH # ## #
注:中间空格不能少,因为####根据最长匹配原则是看作两个##,报错,因为预处理Token不能出现在开头或末尾。
函数式宏定义也有可变参数:
#define showlist(...) printf(#__VA_ARGS__)
#define report(test,...) ((test)?printf(#test):\
printf(__VA_ARGS__))
showlist(The first ,second, and third items.);
report(x>y, "x is %d but y is %d",x,y);
预处理展开,宏定义中可变参数部分用__VA_ARGS__表示:
printf("The first ,second, and third items.");
((x>y)?printf("x>y"):printf("x is %d but y is %d",x,y));
当__VA_ARGS__是空参数时,##运算符会把前面的逗号吃掉。
注:真正的宏应该学习Common Lisp的宏,体会宏的强大!
五、#undef预处理指示
如果用#define重复定义宏,规定这些宏定义必须一模一样,否则报错。如果用#undef取消宏定义,取消一个没有定义的宏不会报错。
六、宏展开的步骤。
举例:#define sh(x)
printf("n" #x "=%d, or %d\n",n##x,alt[x])
#define sub_z 26 sh(sub_z)
展开过程:
1、x的实参是sub_z
2、#x替换为sub_z
3、n##x替换为nsub_z
4、sh(x)展开为:printf("n sub_z =%d, or %d\n",nsub_z,alt[sub_z]);
5、其实sub_z最先替换为26
6、第4步的展开真实是:printf("n 26 =%d, or %d\n",n26,alt[26]);
七、条件预处理指示
实例1:#ifndef HEADER_FILENAME
#define HEADER_FILENAME
/*body of header*/
#endif
实例2:
#if MACHINE == 68000 int x;
#elif MACHINE == 8086 long x;
#else #error UNKOWN TARGET MACHINE #endif
实例3:
#ifdef HEADER_FILENAME
#define HEADER_FILENAME
#undef HEADER_FILENUM
/*body of header*/
#endif
实例4:
#if defined x
等于
#ifdef x
#if !defined x
等于
#ifndef x
实例5:
#if 0
....
#endif
八、其它预处理特性
- #pragma预处理指示供编译器实现一些扩展特性,C标准没有规定#pragma后面写什麽和起什麽作用,由编译器自己决定
- 宏__FILE__代表当前源文件的文件名
- 宏__LINE__代表展开成当前代码行的行号,是编译器内建的特殊宏定义
- C标准规定C标准库的头文件是相互独立的
- C99新特殊标识符__func__是一个变量名不是宏定义,因此不在预处理阶段求值,显示上一个调用的函数名称