总述
在ANSI C的任何一种实现中,存在两个不同的环境。一种是翻译环境,第二种是执行环境。我们代码实现需要经过两种大的步骤——编译和运行。以C语言程序为例,我们写完代码后,需要经过编译和运行才能完成整个过程,而编译又包含4个步骤,分别为:预编译,编译,汇编,链接。当连接完成后,就可以执行运行操作。
预编译
预处理操作简单的解释就是我们在进行编译(总过程)中进行的准备过程,举个例子,我们在床上准备吃午饭,我们需要做好穿衣服的准备工作才可以去吃饭,那么具体的准备工作有哪些呢?
#define
举个例子:
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
#define是最为常见的预处理符号,我们在初始C语言中曾简单介绍过#define的简单用法,MAX此时是常量,为1000,预编译会将代码中出现的MAX全部替换为1000,而reg则是register的替换,同样预处理会将reg全部替换成register。
预定义符号 | 解释 |
---|
_FILE_ | 进行编译的源文件 | _LINE_ | 文件当前的行号 | _DATE_ | 文件被编译的日期 | _TIME_ | 文件被编译的时间 | _STDC_ | 如果编译器遵循ANSI C,其值为1,否则未定义 |
关于#define还有一个注意点,就是宏的概念,举个例子:
#define MAX(a, b) ((a)>(b)?(a):(b))
这个宏是用于比较两个数的大小的,那么为什么不用函数来完成这个任务呢?原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
比较函数和宏:
属性 | #define宏定义 | 函数 |
---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 | 执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 | 操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 | 带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 | 参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。 | 调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 | 递归 | 宏是不能递归的 | 函数是可以递归的 |
头文件
预处理也会处理头文件,关于头文件,我们会有这样一个问题,就是头文件有时会用<>,有时也会使用"",举个例子:
#include <stdio.h>
#include "stdio.h"
当我们用#include "stdio.h"时,预处理会在工程文件夹寻找自己编写的头文件,如果没有发现再去头文件中寻找相对应的库函数,而#include <stdio.h>不一样,<>的引用预处理就只会在库函数中寻找,如果找不到就会报错。
编译
编译过程就会进行语法分析,词法分析,语义分析,符号分析,我们通常报错等的问题就会出现在这个步骤。
语法分析
编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。编译程序的语法规则可用上下文无关文法来刻画。
词法分析
词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。执行词法分析的程序称为词法分析程序或扫描器。 源程序中的单词符号经扫描器分析,一般产生二元式:单词种别;单词自身的值。单词种别通常用整数编码,如果一个种别只含一个单词符号,那么对这个单词符号,种别编码就完全代表它自身的值了。若一个种别含有许多个单词符号,那么,对于它的每个单词符号,除了给出种别编码以外,还应给出自身的值。
汇编
汇编过程会形成符号表,将汇编指令转化为二进制指令 汇编指令是在编译中形成,汇编指令是汇编语言中使用的一些操作符和助记符,还包括一些伪指令(如assume,end),汇编指令同机器指令一一对应。每一种CPU都有自己的汇编指令集。我们知道机器只能识别二进制指令,所以汇编过程就是将汇编指令转化为二进制指令。
链接
链接过程就是将之前形成的符号表进行合并,形成一个完成的可执行程序。 整个程序的执行过程就是这样的,当然还有一些细节方面的问题,希望能给大家带来帮助,谢谢各位。
|