FROM:http://www.cppblog.com/changshoumeng/articles/107160.html
预处理过程
扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理
。
在
C
语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。
要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器
的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符
。
预处理指令是以
#
号开头的代码行
。
#
号必须是该行除了任何空白字符外的第一个字符。
#
后是指令关键字,在关键字和
#
号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令:
1
|
#
|
空指令,无任何效果
|
2
|
#include
|
包含一个源代码文件
|
3
|
#define
|
定义宏
|
4
|
#undef
|
取消已定义的宏
|
5
|
#if
|
如果给定条件为真,则编译下面代码
|
6
|
#ifdef
|
如果宏已经定义,则编译下面代码
|
7
|
#ifndef
|
如果宏没有定义,则编译下面代码
|
8
|
#elif
|
如果前面的
#if
给定条件不为真,当前条件为真,则编译下面代码
|
9
|
#endif
|
结束一个
#if……#else
条件编译块
|
10
|
#error
|
停止编译并显示错误信息
|
一、文件包含
#include
预处理指令的作用是在指令处展开被包含的文件。
包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准
C
编译器至少支持八重嵌套包含。
预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如
:
#define AAA
#include "t.c"
#undef
AAA
#include "t.c"
为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如
:
/*my.h*/
#ifndef
MY_H
#define MY_H
……
#endif
在程序中包含头文件有两种格式:
#include <my.h>
#include "my.h"
第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。
第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。
采用两种不同包含格式的理由在于,编译器是安装在公共子目录
下的,而被编译的应用程序是在它们自己的私有子目录下
的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。
二、宏
宏定义了一个代表特定内容的标识符。
预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号
。宏的第二种用法是定义带参数的宏,这样的宏可以象函数一样被调用,但它是在调用语句处展开宏,并用调用时的实际参数来代替定义中的形式参数。
1.#define
指令
#define
预处理指令是用来定义宏的。该指令最简单的格式是:首先神明一个标识符,然后给出这个标识符代表的代码。在后面的源代码中,就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来,赋给一些记忆标识符。
#define MAX_NUM 10
int array[MAX_NUM];
for(i=0;i<MAX_NUM;i++) /*……*/
在这个例子中,对于阅读该程序的人来说,符号
MAX_NUM
就有特定的含义,它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定,习惯上总是全部用大写字母来定义宏,这样易于把程序的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小,只需要更改宏定义并重新编译程序即可。
宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。例如:
#define ONE 1
#define TWO 2
#define THREE (ONE+TWO)
注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的
。例如:
six=THREE*TWO;
预处理过程把上面的一行代码转换成:
six=(ONE+TWO)*TWO;
如果没有那个括号,就转换成
six=ONE+TWO*TWO;
了。
宏还可以代表一个字符串常量,例如:
#define VERSION "Version 1.0
Copyright(c) 2003"
2.
带参数的
#define
指令
带参数的宏和函数调用看起来有些相似。看一个例子
:
#define Cube(x)
(x)*(x)*(x)
可以是任何数字表达式甚至函数调用来代替参数
x
。这里再次提醒大家注意括号的使用
。宏展开后完全包含在一对括号中,而且参数也包含在括号中,这样就保证了宏和参数的完整性。看一个用法:
int num=8+2;
volume=Cube(num);
展开后为
(8+2)*(8+2)*(8+2);
如果没有那些括号就变为
8+2*8+2*8+2
了。
下面的用法是不安全的:
volume=Cube(num++);
如果
Cube
是一个函数,上面的写法是可以理解的。但是,因为
Cube
是一个宏,所以会产生副作用。这里的参数不是简单的表达式,它们将产生意想不到的结果。它们展开后是这样的:
volume=(num++)*(num++)*(num++);
很显然,结果是
10*11*12,
而不是
10*10*10;
那么怎样安全的使用
Cube
宏呢?必须把可能产生副作用的操作移到宏调用的外面进行:
int num=8+2;
volume=Cube(num);
num++;
3.#
运算符
出现在宏定义中的
#
运算符把跟在其后的参数转换成一个字符串。有时把这种用法的
#
称为字符串化运算符。
例如
:
#define PASTE(n) "adhfkj"#n
main()
{
printf("%s\n",PASTE(15));
}
宏定义中的
#
运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是
adhfkj15
。
4.##
运算符
##
运算符用于把参数连接到一起。预处理程序把出现在
##
两侧的参数合并成一个符号
。看下面的例子
:
#define NUM(a,b,c) a##b##c
#define
STR(a,b,c) a##b##c
main()
{
printf("%d\n",NUM(1,2,3));
printf("%s\n",STR("aa","bb","cc"));
}
最后程序的输出为
:
123
aabbcc
千万别担心,除非需要或者宏的用法恰好和手头的工作相关,否则很少有程序员会知道
##
运算符。绝大多数程序员从来没用过它。
三、条件编译指令
条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
1.#if
指令
#if
指令检测跟在关键字后的常量表达式。如果表达式为真,则编译后面的代码,直到出现
#else
、
#elif
或
#endif
为止;否则就不编译。
2.#endif
指令
#endif
用于终止
#if
预处理指令。
#define DEBUG 0
main()
{
#if DEBUG
printf("Debugging\n");
#endif
printf("Running\n");
}
由于程序定义
DEBUG
宏代表
0
,所以
#if
条件为假,不编译后面的代码直到
#endif
,所以程序直接输出
Running
。
如果去掉
#define
语句,效果是一样的。
3.#ifdef
和
#ifndef
#define DEBUG
main()
{
#ifdef
DEBUG
printf("yes\n");
#endif
#ifndef DEBUG
printf("no\n");
#endif
}
#if defined
等价于
#ifdef; #if !defined
等价于
#ifndef
4.#else
指令
#else
指令用于某个
#if
指令之后,当前面的
#if
指令的条件不为真时,就编译
#else
后面的代码。
#endif
指令将中指上面的条件块。
#define DEBUG
main()
{
#ifdef
DEBUG
printf("Debugging\n");
#else
printf("Not debugging\n");
#endif
printf("Running\n");
}
5.#elif
指令
#elif
预处理指令综合了
#else
和
#if
指令的作用。
#define TWO
main()
{
#ifdef
ONE
printf("1\n");
#elif defined
TWO
printf("2\n");
#else
printf("3\n");
#endif
}
程序很好理解,最后输出结果是
2
。
6.
其他一些标准指令
#error
指令将使编译器显示
一条错误
信息,然后停止编译
。
#line
指令可以改变
编译器用来指出警告和错误信息的文件号和行号
。
#pragma
指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。
分享到:
相关推荐
### C语言条件编译详解 #### 一、预处理与条件编译的引入 C语言的条件编译是一种强大的工具,允许程序员根据不同的条件选择性地编译代码的一部分。这在开发多平台软件、调试、优化以及配置特定功能时特别有用。...
C语言条件编译详解 C语言条件编译是指在编译时根据条件决定编译代码的某些部分。这种机制可以实现以下几点功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。 在C语言中,并没有任何...
c语言编译过程详解.docx ,c语言编译过程详解.docx
本资源“C语言设计实例详解”旨在通过丰富的实例来帮助学习者深入理解和掌握C语言的核心概念与编程技巧。 首先,C语言的基础知识包括变量、数据类型、运算符、控制结构等。变量是用来存储数据的容器,数据类型如...
预处理器、宏定义和头文件也是C语言的重要组成部分,书中可能涉及预处理器指令的使用,如#include、#define、条件编译等,这些在编写大型项目时尤为关键。 文件I/O是C语言进行数据持久化存储的方法,书中可能详细...
C语言预编译指令详解 概述: C语言预编译指令是一种强大的工具,允许开发者根据不同的情况生成不同的软件版本。通过设置预编译指令,可以在编译时选择编译不同的代码,从而实现条件编译。 一、条件编译方法 1. #...
8. **预处理器**:宏定义、条件编译等预处理指令的理解与应用。 9. **文件操作**:文件打开、读写、关闭,以及文件指针的操作。 10. **错误处理**:了解如何使用errno和perror处理程序运行时的错误。 深入学习...
### C语言预编译教程详解 #### 宏定义——C语言的强大工具 宏定义是C语言预编译过程中的一个重要特性,它允许程序员在编译前定义文本替换规则,从而增强代码的可读性、可维护性和灵活性。宏定义分为无参数宏定义和...
《单片机智能化产品C语言设计实例详解》是一本深入探讨单片机应用开发的专业书籍,专注于使用C语言进行智能产品的设计与实现。在当前信息化社会中,单片机技术作为电子设备的核心部分,广泛应用于各种智能设备和控制...
C语言中条件编译详解 C语言中条件编译是指通过预处理程序提供的条件编译功能,根据不同的条件编译不同的程序部分,从而产生不同的目标代码文件。这对于程序的移植和调试是非常有用的。 条件编译有三种形式: 1. ...
### C语言编译原理详解 #### 一、预处理阶段(Preprocessing) 在C语言的编译过程中,预处理是一个非常关键的初始阶段。在这个阶段,预处理器对源代码进行了一系列的操作,为后续的编译做好准备。预处理的具体任务...
### C语言-编译预处理知识点详解 ...通过上述对C语言编译预处理知识点的详细介绍,我们可以看出,编译预处理在C语言开发中扮演着重要的角色,合理地使用这些特性能够显著提高代码的可读性、可维护性和跨平台能力。
总的来说,这个"PL0 C语言版 编译原理"项目是一个实用的教学资源,它让学习者能够亲手构建一个小型的编译器,从而深入理解编译器的内部工作机制。通过研究和调试这个C语言实现的PL0编译器,不仅可以掌握编译技术,还...
C语言宏条件编译详解 C语言宏条件编译是指在编译过程中,根据条件选择性地编译代码的一种机制。这种机制允许开发者根据不同的条件编译不同的代码,从而实现代码的灵活性和可重用性。 预处理过程是C语言编译器的前...
详细讲解c语言的编译过程,并附有图解 很好的资料
根据提供的信息,《C语言开发技术详解》这本书主要聚焦于C语言的开发技术和详细的解析。由于提供的部分内容并未包含实际的知识点,下面将基于标题、描述以及常见的C语言开发知识点来进行详细的阐述。 ### C语言简介...
《C语言开发实例详解》是一本专注于通过实例教学C语言编程的书籍。C语言作为基础且广泛应用的编程语言,对于初学者和资深开发者都具有重要价值。本书旨在帮助读者深入理解C语言的核心概念,掌握其语法特性,并通过...
C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作...
在C语言中,PCRE库提供了几个关键函数,包括`regcomp()`、`regexec()`和`regfree()`,用于编译、匹配和释放正则表达式。 1. `regcomp(regex_t *compiled, const char *pattern, int cflags)` 这个函数用于将给定的...