开启掘金成长之旅!这是我参与「掘金日新计划 · 13 月更文挑战」的第14天,点击查看活动详情
C 预处理器
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
| 指令 | 描述 |
|---|---|
| #define | 定义宏 |
| #include | 包含一个源代码文件 |
| #undef | 取消已定义的宏 |
| #ifdef | 如果宏已经定义,则返回真 |
| #ifndef | 如果宏没有定义,则返回真 |
| #if | 如果给定条件为真,则编译下面代码 |
| #else | #if 的替代方案 |
| #elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
| #endif | 结束一个 #if……#else 条件编译块 |
| #error | 当遇到标准错误时,输出错误消息 |
| #pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
预处理器实例
分析下面的实例来理解不同的指令。
#define MAX_ARRAY_LENGTH 20
取消宏定义和宏的重定义
·使用#undef命令,语法:
#undef 宏名
·避免宏的重定义,使用#ifndef或#ifdef命令,例
#ifndef MYHEADER_H
#define MYHEADER_H 2000
#endif
宏展开的优先级错误
·宏的操作完全是标记的文本替换,只有在宏展开结束后,才会把宏体解析为声明,表达式或语句,例:
#define SQUARE(x) xx
SQUARE(a)=>aa //ok
SQUARE(a+b)=>a+ba+b //???
·避免这种事件发生,可以使用括号
#define SQUARE(x) (x)(x)
SQUARE(a+b)=>(a+b)*(a+b) //ok
把标记转换为字符串
·在标记序列中使用#,例
#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b) )
TEST(0,0xFFFF)=>printf("0<0xFFFF=%d\n", (0)<(0xFFFF))
宏展开中的标记合并
·在标中使用##,例
#define NAME(i) name ## i
NAME(1)=>name1
宏的可变参数列表
·使用省略号来表示宏参数中的可变参数列表,语法:
#define 宏名(参数列表,...) 标记序列
#define 宏名(...) 标记序列
·在标记序列中使用__VA__ARGS__来对应参数列表中的...,例
#define MAKE_EM_A_STRING(...) #VA__ARGS
MAKE_EM_A_STRING(a,b,c,d)=>"a,b,c,d"
文件包含
·使用#include命令,形式如下:
#include <字符序列>
#include "字符序列"
#include 标记序列
·使用<>和""的区别在于编译器查找包含文件的方式
<>:根据编译器的定义规则和路径进行查找
"":从源文件的当前路径开始查找,如果不存在,则以<>形式来处理
条件编译
·使用#if,#else,#endif命令,形式:
#if 常量表达式
....
#else
...
#endif
·使用#elif命令,形式
#if 常量表达式1
...
#elif 常量表达式2
...
#elif 常量达式n
...
#else
...
#endif
·使用#ifdef和#ifndef命令,形式
#ifdef 宏名
表示如果该宏名已被定义,则相当于#if 1
如果该宏名没有定义,则相当于#if 0
·defined操作符
·defined操作符可以在#if和#elif表达式中使用,不能用于别处,形式
defined name
defined (name)
例:
#if defined(VAX) 代替
#ifdef VAX
显示的行号
·使用#line命令,形式:
#line n "filename"
#line n
表示源代码的下一行来自用户所编写的filename的文件的第n行,n必须是十进制数字
pragma指定
·以添加新的预处理器功能或者向编译器提供因实现而异的信息
·标准pragma命令
#pragma [FP_CONTRACT|FENV_ACCESS|CX_LIMITED_RANGE] [ON|OFF|DEFAULT]
·_Pragma操作符,形式:
_Pragma("字符串序列")
例:
_Pragma("STDC FENV_ACCESS ON") 等价于
#pragma STDC FENV_ACCESS ON
错误指令
·使用#error命令,形式
#error 预处理器标记
·如果信息中包含有宏,而不想将宏进行展开,可以使用字符串形式
#define SIZE 1024
#if (SIZE%256 !=0 )
#error "SIZE must be a multiple of 256"
#endif
关于一些旧编译器空白字符的预处理
例:
#define INC ++
#define TAB internal_table
#define INCTAB table_of _increments
#define CONC(x,y) x/**/y
对于CONC(INC,TAB)进行展开,标准与非标准编译器处理方式:
标准 非标准
CONC(INC,TAB) CONC(INC,TAB)
INC TAB INCTAB 这里出现了不一样的结果
++ internal_table table_of_increments 导致结果也不一样了