预处理阶段,预处理器查找一行中以#
号开始的预处理指令。ANSI和后来的标准都允许#
号前面有空格或制表符,而且还允许在#
和指令的其余部分之间有空格。
明示常量
#define 定义符号常量,还有其它用途
#undef 取消宏的定义
在#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数。
文件包含
#include 将被包含文件的内容输入到源文件#include指令所在的位置
包含一个大型头文件不一定显著增加程序的大小。在大部分情况下,头文件的内容是编译器生成最终代码时所需的信息,而不是添加到最终代码中的材料。
条件编译
#ifdef
、#else
和#endif
指令
#ifdef
判断某个宏已经用#define定义,若已定义,则执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令就执行到哪里)。如果预处理器未定义某个宏,且有#else指令,则执行#else和#endif指令之间的所有代码。
#ifdef #esle很像C的if else。两者的区别是预处理器不识别用于标记块的花括号({}),因此它使用#else和#endif来标记指令块。使用时#else不一定存在,#endif必须存在。这些指令结构可以嵌套。
#ifndef
指令
#ifndef指令与#ifdef指令的用法类似,,但是它们的逻辑相反,#ifndef指令判断后面的标识符是否是未定义的。
#ifndef指令可以防止相同的宏被重复定义,例如防止多次包含一个文件。
#if
和#elif
指令
#if很像C语言中的if,#elif很像C语言中的else-if。#if后面跟整形常量表达式,如果表达式为非零,则表达式为真。可以在指令中使用C的关系符和逻辑运算符。
可以按照if else的形式使用#elif,如:
#if SYS == 1
#include "ibmpc.h"
#elif SYS == 2
#include "vax.h"
#elif SYS == 3
#include "mac.h"
#else
#include "general.h"
#endif
较新的编译器提供另一种方法测试名称是否已定义,即用#if defined (VAX)
代替#ifdef VAX
。defined
是一个预处理运算符,如果它的参数是用#define定义过,则返回1;否则返回。这种新方法的优点是,它可以和#elif一起使用。如:
#if defined(IBMPC)
#include "ibmpc.h"
#elif defined(VAX)
#include "vax.h"
#elif defined(MAC)
#include "mac.h"
#else
#include "general.h"
#endif
如果在VAX机上运行这几行代码,那么应该在文件前面用下面的代码定义VAX:
#define VAX
条件编译让程序移植更容易,改变文件开头部分的几个关键的定义,即可根据不同的系统设置不同的值和包含不同的文件。
预定义宏
C标准规定了一些预定义宏。
宏 | 含义 |
---|---|
__DATE__ | 预处理的日期("Mmm dd yyyy"形式的字符串字面量,如Nov 23 2013) |
__FILE__ | 表示当前源代码文件名的字符串字面量 |
__LINE__ | 表示当前源代码文件行号的整形常量 |
__STDC__ | 设置为1时,表面实现遵循C标准 |
__STDC_HOSTED__ | 本机环境设置为1;否则设置为0 |
__STDC_VERSION__ | 支持C99标准,设置为199901L;支持C11标准,设置为201112L |
__TIME__ | 编译代码的时间,格式为"hh:mm:ss" |
C99标准提供一个名为__func__
的预定义标识符,它展开为一个代表函数名的字符串(该函数包含该标识符)。那么,__func__
必须具有函数作用域,而从本质上看宏具有文件作用域。因此,__func__
是C语言的预定义标识符,而不是预定义宏。
注意,C99新增的预定义宏和预定义标识符,不支持C99的编译器可能无法识别它们。如果使用GCC,必须设置-std=c99
或-std=c11
。
#line和#error
#line指令重置__LINE__和__FILE__宏报告的行号和文件名。
#line 1000 /* 把当前行号重置为1000 */
#line 10 "cool.c" /* 把行号重置为10,把文件名重置为cool.c */
#
#error指令让预处理器发出一条错误消息,该消息包含指令中的文本,如果可能的话,编译过程应该中断。
/* newish.c */
#if __STD_VERSION__ != 201112L
#error Not C11
#endif
gcc newish.c
输出
newish.c :2:2:error: #error Not C11
#pragma
在所有预处理指令中,#pragma指令可能是最复杂的了,它的作用是修改编译器的一些设置。它是另一种机制,用于支持因编译器而异的特性。它的语法也是因编译器而异。其一般格式为:
#pragma para
其中para为参数。
在开发过程中要用到的预编译指令#pragma要进行统一的管理和维护,要求把所有的预编译指令放到一个文档中,做成一个列表的形式,并且对每条指令进行详细的解释。不仅要说明它的使用方法、含义、作用,还要说明是在哪个函数里面用到的等等。总之,是要让别人很全面地了解你的想法,对其他使用者起到一个支持和辅助的作用。当然,如果有编译器指令的删除、修改或者添加操作,都要及时地对这个文档进行更新。而且由于#pragma是与编译器相关的,不同的编译器有不同的定义,所以,在使用过程中需要把它用函数封装起来,或者用宏定义的形式定义好,统一放到一个文件管理,而不是直接出现在函数里面。这样当编译器更改时,只需要改动一个文件或者相关函数即可。
#和##
用宏参数创建字符串:#运算符
C允许在字符串中包含宏参数。在类函数宏的替换中,#号作为一个预处理运算符,可以把记号转换为字符串。
预处理器粘合剂:##运算符
与#运算符类似,##运算符可用于类函数宏的替换部分。而且,##还可用于对象的替换部分。##运算符把两个记号组合成一个记号。