前  言

C语言源程序是先经过预处理器进行预处理,然后经过编译器编译成二进制可执行代码。

一般将编译器套件、集成开发工具这两种工具进行集成,预处理过程和编译过程一并处理。

C语言预处理是通过预处理指令实现。每个预处理指令以#符号开头,以行为单位,来实现编译前的准备工作。例如文件条件引入、宏替换和条件编译。

预处理指令

​C语言提供以下预处理指令。

:预处理指令必须以#开头,#符号必须是当前行的第一个非空白字符。

:预处理指令以一行作为处理单位,每行预处理指令是有多个预处理单词组成,单词之间使用空格或横向制表符分割。

预处理指令包括:

#include:包含一个指定的源文件

#define  :定义一个宏

#undef    :取消已定义的宏

• 条件编译 :#if #ifdef #ifndef #elif #else #endif

#line  :产生下一代码行号和源文件名称

#error :产生一个诊断错误信息

#pragma :编译指示,预处理器实现自定义的编译动作

•     #    :空指示,不起作用

文件包含

编译前引入特定的文件内容当前的源代码文件中一起编译(主要用来引入头文件或其他源代码文件)。

有以下三种形式:

第1种形式:

#include <文件名>   //一般从系统路径位置寻找引入的头文件。

第2种形式

#include "文件名"  //一般是先在用户定义的路径寻找该文件,如果找不到再去系统路径寻找。

第3种形式

#include 任意的预处理单词
//该形式下的预处理单词也按照普通正文进行处理,处理完成以后,应和前两种之一匹配,再进行处理。

例如:

    #include <stdio.h>
#include "mystdio.h"

#define MYFILE "mystdio.h"
#include MYFILE

宏替换

(英文:Macro),根据一系列预定义的规则替换一定的文本模式,作用是将一个简单的小命令或动作转化为一系列指令,C语言中的宏的作用就是在宏出现的位置展开替换的文本。

语法:

#define  宏名  替换文本  // 简单替换形式
#define 宏名(参数) 替换文本 // 带参数的宏,类似函数调用

例如:

#define MAX_LEN 100
char str[MAX_LEN];

//定义常量,使用宏定义字符数组最大长度,出现MAX_LEN全部替换成100,当需要修改数组长度时,只需要修改宏定义即可。
#define MAX_LEN 100
#undef MAX_LEN
char str2[MAX_LEN];

//取消MAX_LEN宏定义,在#undef命令之后,就不能使用该宏,否则编译出错。
#define SUM(a, b) (a+b)
int s = SUM(1,2);
double d = SUM(100.01,200.01)

//类似函数,使用宏定义计算参数a和b的和。

​条件编译

根据条件(例如不同硬件平台),选择不同的代码进行编译,方便程序移植和跨平台。

if条件有三种形式:

#if 常量表达式
#ifdef 宏名称
#ifndef 宏名称

根据if条件值,当值为0时,忽略和不编译后面代码文本。

注:#if后面必须是常量表达式,不能是函数调用和变量。

其中#if可包含以下条件:

#if defined 宏名称
#if defined (宏名称)

#ifdef 等价于 #if defined
#if ! defined 等价于 #ifndef

else条件三种形式:

#elif:#else和#if的结合,后面常量表达式
#else:与#if配合使用
#endif:结束#if或#else条件块

例如:

#ifndef MYHEADER_H
#define MYHEADER_H "myheader.h"
// 头文件内容
#endif

//避免重复引入同一个文件多次,可以在被引入的头文件开头加入添加编译。

例如:不同硬件平台使用数字代替,x86=0,x64=1,arm=2。

#define X86 0
#define X64 1
#define ARM 2

#define ARCH 0
#if ARCH==0
#include "x86.h"
#elif ARCH==1
#include "x64.h"
#elif ARCH==2
#include "arm.h"
#else
#include "others.h"
#endif

或者:

#define DEBUG 
#ifdef DEBUG
// 执行调试模式下的代码
#else
// 执行非调试模式下的代码
#endif

等价于:

#define DEBUG 
#if defined DEBUG
// 执行调试模式下的代码
#else
// 执行非调试模式下的代码
#endif

行控制

预处理指令#line 用来控制编译器编译时警告和错误信息的行号和文件名。

两种形式:

1.#line 整数n

设置当前#line所在行的行号是整数n,后面的代码行均从此整数计数。

例如:

#include <stdio.h>
int main(){
int *p = NULL;
#line 30
*p = 0 return 0;
}

编译出错:提示是31行出错,*p=0 末尾缺少分号,出错的文件名默认是当前文件名。

2.# line 整数n 文件名称

设置当前#line所在行的行号和当前文件名。

# include <stdio.h>
int main(){
int *p = NULL;
#line 30 "mytest.c"
*p = 0
return 0;
}

编译出错:提示是mytest.c文件的31行出错,*p=0 末尾缺少分号。

出错处理指示

预处理指令#error用来控制编译器停止编译,显示特定的出错信息。

例如:

# include <stdio.h>
int main(){
#error "test error"
return 0;
}

编译出错:显示 "test error"

编译指示

预处理指令#pragma,各种编译器实现自定义预处理功能,用于扩展,例如禁用编译器的一些警告信息,拓展一些编译功能,标准没有定义具体的指令,需要根据不同编译器文档使用。

系统预处理的宏名

预定义的宏以两个下划线开头​​__​​。

__LINE__当前源代码的行号
__FILE__当前的源代码文件名称
__DATE__编译日期,格式是Mmm dd yyyy
__TIME__编译时间,格式是hh:mm:ss
__STDC__表示当前编译器是否符合标准,1表示符合标准

例如:

# include <stdio.h>
int main(){
printf("%d\n",__LINE__);
printf("%s\n",__FILE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
printf("%d\n",__STDC__);
return 0;
}

运行结果:

4
D:\Dev_C_Code\C_2.c
Aug 8 2022
13:50:22
1

有关宏中的#和##运算符

宏中的#运算符,将宏参数字符串常量化,具体是将#和跟在其后的参数转换成字符串,自动加入双引号和转义特殊字符。

例如:

# include <stdio.h>
# define TEST(s) "012345"#s"789\n"
int main(void){
printf(TEST(6));
printf(TEST("6"));
return 0;
}
输出:0123456789
012345"6"789

:​​##​​ 运算符将多个宏参数的值直接连接起来(不转换为字符串),

​##​​出现的两个宏参数中间,不能出现替换文本的开头和末尾。

例如:

# include <stdio.h>
# define TEST(s1,s2,s3) s1##s2##s3
int main(void){
printf("%d\n",TEST(1,2,3));
return 0;
}

输出:​​123​​。

------------ END ------------