C语言学习记录(17)编译和链接
一、翻译环境和运行环境
编程语言的最开始就是二进制(机器语言),因为计算机的硬件只能识别0和1(比如电路信号的有无),但是如果我们编写程序的时候你写一大堆0和1,别说给别人看了,你自己睡一觉第二天起来还能看得懂吗;二进制语言发展以后出现了汇编语言,有了简单的命令:ADD MOV等。但是即使有了这些简单的命令,仅仅了解这些也还是不够的,这个时期不仅需要熟悉命令,而且对计算机的硬件也要足够的了解;于是就有了编程语言的雏形,可以通过自然语言的语法描述需要程序执行的操作,而发展到现在底层这些如果不懂的话,也不太影响你去编写程序,让人好像跟计算机讲故事一样描述命令。
而这次要大概了解的内容就是我们编写的代码如果最终变成计算机可以识别的语言并最终运行起来(只是粗浅的了解一下,因为目前使用的编译器很难展示这种非常底层的东西)
我们平常编译并运行就包括了两个大块:翻译和运行。
翻译就是将我们的编程语言翻译为机器语言,运行就是让机器语言跑起来。
而这两个环节需要两个环境,一个是翻译环境,一个是运行环境。
二、翻译环境
翻译环境是将编程语言转换成机器语言。
整个翻译的环节都进行了什么操作呢?
大类上看来分为两类:编译和链接。
编译又可以细分为:预处理(预编译)、编译、汇编、链接。
1.编译
①预处理(预编译)
这个环节是为了下一步的编程语言转换为汇编语言做了提前的准备,例如:
删去所有的#define定义并展开所有宏
我们可以#define一些特殊含义的词,赋予其值,如:
在预编译的时候就会直接删去#define这句话,同时替换这个定义,即预处理后MAX的位置显示的就是100。
这句话是人为规定的常量,不存在于汇编语言中,所以提前给他替换了。
处理#include的头文件内容,插入到所在文件的相对位置
说的不知所云,什么意思呢?
stdio.h这个头文件中包含了这么多的函数以及其他的内容,计算机可不会说你调用printf这个函数,那我就从头文件里拿出来这个函数,而是直接把包含的这么多东西直接放在相应位置--
#include <stdio.h>后面有效的语句是main函数的内容(因为#define也得被删),那么最后这个头文件包含的那么多的函数全部都放在main函数之前。
所以这就是为什么使用库函数之前要提前包含头文件,预处理的时候会把这句话换成已经写好的函数,这个时候跟调用自己写的函数一个道理,我用的时候你前面必须已经有了。
删除所有的注释
一样道理,注释其实不是有效指令,是程序员自己写的便于其他程序员来看的话,转换汇编语言前要将其删去。
添加行号和文件名标识,方便后续编译器生成调试信息等
处理所有的条件编译指令,如: #if 、 #ifdef 、 #elif 、 #else 、 #endif
这个留到下一次再讲。
保留所有的 #pragma 的编译器指令,编译器后续会使用
这个见过一次,在学习结构体的内存对齐的时候,存在一个对齐数的概念
#pragma pack()往这里面输入值,就可以修改默认对齐数,当然不输入值的话还是保持或改回默认对齐数。
经过预处理后的.c文件被转化为.i文件。
②编译
编译过程就是将预处理后的文件进行⼀系列的:词法分析、语法分析、语义分析及优化,生成相应的汇编代码文件。
说着其实抽象完了,比如:
int a = 3 + 5;
先进行词法分析:
a是标识符(也就是程序员起的名字)
=是赋值符号
3是数字
+是加号
5是数字
分析完了以后就语法分析,创建出来个语法树:
最后语义分析:
看见了=,那就是赋值表达式,赋值表达式分开看,从右往左看,右边有+号,是一个加法表达式,+号的两个操作数是3 5,都是整型,所以加法的结果都是整型,最后结果赋给一个int类型的变量,没有逻辑上的错误,类型都符合。
这个时候经常会检查类型,声明等的合法,比如你俩整型的和最后接收的地方是不是存整型的,你要用double a来存,在这个过程中就会报错。
这一步将.i文件变为.s文件。
③汇编
汇编是将汇编代码转变成机器可执行的指令(2进制的指令),每⼀个汇编语句几乎都对应⼀条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译,也不做指令优化。
走到这一步肯定是经过预处理和编译,已经是汇编语言了,下一步就是把汇编语言转换成二进制语言(实际上就是机器语言)。
这一步将.s文件变为.o文件
④链接
按理说二进制语言都搞出来了,也没什么其他的事好干了,但是往往一个工程下面可能有好几个文件,比如:
刚学函数的时候就举过这样的例子,你这样的话,肯定就需要链接一下最终放在一个程序内。
链接是一个复杂的过程,在这里就不多说了。
这一步将所有的.o文件链接在一起,并生成.exe文件,即可执行文件。
我们平常的编译就是包括这么多过程,正常一个F5就完事了,所以细节实在不好描述,不过有一本书————《程序员的自我修养》,里面就非常详细的对这个过程进行讲解,如果有兴趣、用的到,一定的看一看。
三、运行环境
在运行阶段,程序通过操作系统的帮助加载到内存中,操作系统分配内存,启动程序执行。
程序在运行时依赖运行时环境,主要包括:
-
硬件:CPU、内存、存储等。
-
操作系统:管理硬件资源,提供执行支持。
-
编程语言的运行时:如Java的JRE、.NET的CLR,提供语言特定功能。
-
库与框架:为程序提供现成的功能支持。
这段复制的ai的,运行环境说起来就更复杂了,暂时用不到,.exe的程序可以执行,知道这个就够了。