编译预处理包含的逻辑会在程序的编译阶段执行,而非执行阶段。C语言中,常见的编译预处理操作包括宏定义、内联函数、预处理指令等。编译预处理和编译器相关,各编译器可能支持不同的特性,这里我们只作简单介绍。
变量式宏定义例子:
#define N 20
函数式宏定义例子:
#define MAX(a, b) ((a)>(b)?(a):(b))
k = MAX(i&0x0f, j&0x0f)
//展开是这样的:
k = ((i&0x0f)>(j&0x0f)?(i&0x0f):(j&0x0f))
宏函数和正常定义的函数区别:
MAX
是个真正的函数,那么它的函数体return a > b ? a : b;
要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX
是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。#define MAX(a, b) (a>b?a:b)
,省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f)
,运算的优先级就错了。同样道理,这个宏定义的外层括号也是不能省的。MAX(++a, ++b)
,如果MAX是个真正的函数,a和b只增加一次。但如果MAX是上面那样的宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b))
, a和b就不一定是增加一次还是两次了。内联函数使用inline
关键字修饰。inline
关键字告诉编译器,这个函数的调用要尽可能快,可以当普通的函数调用实现,也可以用宏展开的办法实现(效果就是没有函数调用过程)。
#ifndef HEADER_FILENAME
#define HEADER_FILENAME
/* body of header */
#endif
#if MACHINE == 68000
int x;
#elif MACHINE == 8086
long x;
#else /* all others */
#error UNKNOWN TARGET MACHINE
#endif
#error UNKNOWN TARGETMACHINE
这段代码表示,编译器遇到这个预处理指示就报错退出,错误信息就是UNKNOWN TARGET MACHINE
。
假设上述代码是用来识别处理器平台的,如果要为8086平台编译这段代码,在所有需要配置的源文件开头包含一个头文件,在头文件中定义#define MACHINE 8086
,这样只需要改一个头文件就可以影响所有包含它的源文件。
通常这个头文件由配置工具生成,比如在Linux内核源代码的目录下运行make menuconfig
命令可以出来一个配置菜单,在其中配置的选项会自动转换成头文件include/linux/autoconf.h
中的宏定义。
#pragma
预处理指示供编译器实现一些非标准的特性, C标准没有规定#pragma
后面应该写什么以及起什么作用,由编译器自己规定。__FILE__
和__LINE__
,__FILE__
展开为当前源文件的文件名,是一个字符串,__LINE__
展开为当前代码行的行号,是一个整数。这两个宏在源代码中不同的位置使用会自动取不同的值,显然不是用#define能定义得出来的,它们是编译器内建的特殊的宏。在打印调试信息时打印这两个宏可以给开发者非常有用的提示。__func__
。这个标识符应该是一个变量名而不是宏定义,不属于预处理的范畴,但它的作用和__FILE__
、__LINE__
类似,使用printf("%s\n", __func__)
可以输出函数名。