当我们历经千辛万苦,将解决一些问题的程序代码写好之后,便来到了“程序”人生中最重要的一步,我们创造了程序但是光创造还是不行的,还需要让我们的程序可以运行起来,才能达到我们的目的。
那么一个程序,从编译到运行都要经历那些步骤呢?我们接着往下看:
但是作为一名专业的程序员,对于这个问题我们真不能简单来说,还需要详细的讨论一下,研究以下程序编译链接的到底是如何实现的。程序编译链接的过程:预编译,编译,汇编,链接,运行
预编译:删除所有注释,并将“#”的头文件在当前位置展开,得到一个新的.i文本文件
编译:将.i文本文件通过编译器编程汇编代码,得到一个.s文件
汇编:通过汇编器将.s汇编程序文件变成一个二进制可重定位文件,既.o文件
链接:当我们的程序调用了一些别的.o文件时,例如printf.o文件,这个时候就需要将其连接到我们当前的文件当中,形成一个可执行文件 .exe。
其实当我们的程序写好之后,其实他还不是一个真正的程序,他只是一个普通的文本文件,并且被储存在磁盘当中。那么从一个普通的文本文件到一个可执行文件,我们的程序都经历了那些事情呢?
这里提一下进程的概念吧,后面会用到
进程就是程序的一次执行,是计算机资源分配的单位,但是线程才是计算机资源分配的最小单位
后面关于这一点的介绍我们就是用helloworld.c (这应该是所有的程序员都耳熟能详的程序了,闭着眼睛都能写)来介绍吧。
我们的程序编译的时候看似简单,实则是被编译命令和集成开发环境隐藏了很多的过程。
#includeint main() { printf("HelloWorld!n"); return 0; } //程序虽小蕴含的东西可不少
被隐藏的过程:
- Linux操作系统下预编译的命令如下:
gcc —E hello.c -O hello.i
- 预编译过程主要进行的工作是处理源代码文件中以 # 开头的预编译指令。主要处理规则如下:
预编译完成之后我们会得到一个 .i 文件, .i 文件中不包含任何 “#”开头的文件和宏定义,所有的注释也已经被删除,头文件都被插入到相应的位置。所以当我们无法判断宏定义和头文件包含是否正确时,我们可以查看.i 文件。
到了编译阶段,我们就是将预编译之后的文件进行各种分析以及优化,产生相应的汇编文件,这一步由编译器进行,这一步也可以算是整个编译链接过程的核心部分。
Linux操作系统下编译的命令如下:
gcc —S hello.c -O hello.s在编译的过程中,我们的编译器做了什么呢?
编译一般可以分为六步:扫描,语法分析,语义分析,源代码优化,代码生成和目标代码优化。
- 1 扫描:源程序被输入到扫描器,然后被分割成一系列的记号。扫描的过程只进行简单的词法分析,扫描时产生的记号一般分为以下几类:关键字、标识符,字面量和特殊符号。此外扫描还需要做的工作是,将标识符放到符号表,将字面量放到字符表中以备后面使用。
- 2语法分析:对扫描产生的记号进行语法分析,产生语法树。此时需要确定符号的优先级和含义,如果出现了语法分析不合法,编译器就会报错。
- 3语义分析:语法分析只是仅仅是对表达式进行简单的语法分析,并不会判断表达式是否有意义。有的语句在语法上是合法的,但是确实没有意义的。例如两个指针想乘,语法分析的时候是没有错误的,但是两个指针相乘是没有意义的
- 4源代码优化及目标代码的生成和优化:经过上面一系列的步骤我们已经可以得到相应的目标代码,只需要再进行简单的优化即可得到我们想要的汇编代码,但此时还没有完成。
经过上面的步骤,我们的代码已经成为了一段汇编代码,但是此时我们的机器还是不能够识别的,汇编器的作用就是,将我们的汇编代码转变成机器可以执行的二进制指令。Linux命令如下
gcc -c hello.c -o hello.o链接阶段
我们的代码进行到这一步已经可以被机器所识别并执行了,但是我们一个程序所要完成的功能可能还需要别的已经编译好的.o文件来辅助才可以完成功能。
现代软件的完成往往都有很多的代码构成,这些代码如果都被放在一个模块当中那么是不可想象的,这个时候就开始了分模块,这些模块都是单独编译、汇编、处理的,这些模块相互独立又相互依赖。
如何将一些模块拼装成一个完整的可以执行的程序,就是我们的链接过程。链接是将各个模块之间互相引用的部分处理好,使得各个模块之间能够正确的相互衔接。



