深入理解 C 预处理器 (CPP):它如何简化编译过程
C 预处理器并非编译器的一部分,而是编译过程中的一个独立步骤。简单来说,C 预处理器只是一个文本替换工具,它指示编译器在实际编译前进行必要的预处理。我们将 C 预处理器简称为 CPP。
预处理器指令
所有预处理器命令都以井号(#)开头。它必须是第一个非空白字符,为了可读性,预处理指令应从第一列开始。下表列出了所有重要的预处理指令:
| 序号 | 指令 & 说明 |
|---|---|
| 1 | #define 定义预处理宏(文本替换)。 |
| 2 | #include 插入另一个文件的头文件内容。 |
| 3 | #undef 取消已定义的预处理宏。 |
| 4 | #ifdef 若宏已定义则为真。 |
| 5 | #ifndef 若宏未定义则为真。 |
| 6 | #if 测试编译时条件是否为真。 |
| 7 | #else #if 的替代分支。 |
| 8 | #elif 将 #else 和 #if 合并为一条语句。 |
| 9 | #endif 结束预处理条件编译。 |
| 10 | #error 向 stderr 输出错误信息。 |
| 11 | #pragma 以标准化方式向编译器发布特殊命令。 |
预处理器示例
通过以下示例理解各指令的用法。
#define MAX_ARRAY_LENGTH 20
该指示 CPP 将所有 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量可提高代码可读性。
#include <stdio.h> #include "myheader.h"
第一行指示 CPP 从系统库获取 stdio.h 并将其文本插入当前源文件。第二行指示 CPP 从本地目录获取 myheader.h 并插入内容。
#undef FILE_SIZE #define FILE_SIZE 42
指示 CPP 取消现有的 FILE_SIZE 定义,并将其重新定义为 42。
#ifndef MESSAGE #define MESSAGE "You wish!" #endif
仅当 MESSAGE 尚未定义时,才将其定义为 "You wish!"。
#ifdef DEBUG /* 你的调试语句 */ #endif
若定义了 DEBUG,则处理其中的语句。这在编译时向 gcc 传递 -DDEBUG 标志时很有用,可随时开启或关闭调试功能。
预定义宏
ANSI C 定义了一些宏。虽然它们可在编程中使用,但预定义宏不应被直接修改。
| 序号 | 宏 & 说明 |
|---|---|
| 1 | __DATE__ 当前日期,格式为 "MMM DD YYYY" 的字符字面量。 |
| 2 | __TIME__ 当前时间,格式为 "HH:MM:SS" 的字符字面量。 |
| 3 | __FILE__ 当前文件名的字符串字面量。 |
| 4 | __LINE__ 当前行号的十进制常量。 |
| 5 | __STDC__ 编译器符合 ANSI 标准时定义为 1。 |
尝试以下示例:
Live Demo#include <stdio.h>
int main() {
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
在文件 test.c 中编译执行上述代码,输出如下:
File :test.c Date :Jun 2 2012 Time :03:36:24 Line :8 ANSI :1
预处理器运算符
C 预处理器提供以下运算符以辅助创建宏:
宏续行 (\) 运算符
宏通常限制在单行内。宏续行运算符(\)用于将过长的宏延续到下一行。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
字符串化 (#) 运算符
字符串化运算符('#')在宏定义中使用时,可将宏参数转换为字符串常量。该运算符仅用于具有指定参数列表的宏。例如:
Live Demo#include <stdio.h>
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void) {
message_for(Carole, Debra);
return 0;
}
编译执行后输出:
Carole and Debra: We love you!
标记粘贴 (##) 运算符
标记粘贴运算符(##)可在宏定义中将两个参数合并。它允许宏定义中的两个独立标记连接成单个标记。例如:
Live Demo#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void) {
int token34 = 40;
tokenpaster(34);
return 0;
}
编译执行后输出:
token34 = 40
这是因为预处理器实际输出如下:
printf ("token34 = %d", token34);
此例展示了 token##n 连接成 token34,并同时使用了字符串化和标记粘贴。
Defined() 运算符
预处理器 defined 运算符用于常量表达式,判断标识符是否已通过 #define 定义。若已定义则值为真(非零),否则为假(零)。用法如下:
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void) {
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
编译执行后输出:
Here is the message: You wish!
参数化宏
CPP 的强大功能之一是使用参数化宏模拟函数。例如,计算平方的函数:
int square(int x) {
return x * x;
}
可用宏重写为:
#define square(x) ((x) * (x))
带参数的宏必须在使用前用 #define 定义。参数列表用括号括起,必须紧跟宏名,中间不能有空格。例如:
Live Demo#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void) {
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
编译执行后输出:
Max between 20 and 10 is 20
C语言