-
编译(Compilation):首先,程序员使用文本编辑器编写源代码,然后通过编译器将源代码转换为目标代码。编译器会进行词法分析、语法分析、优化和代码生成等步骤,最终生成可执行文件或库文件。
-
链接(Linking):如果程序包含多个源文件或库文件,链接器会将这些文件中的目标代码连接在一起,形成最终的可执行文件。链接器还会解决符号引用、符号重定位等问题,确保程序能够正确运行。
-
加载(Loading):当用户要运行一个程序时,操作系统会负责将程序的可执行文件加载到内存中,并为程序分配所需的资源。加载程序到内存后,操作系统会启动程序的执行。
-
执行(Execution):CPU根据程序的指令集架构(如x86、ARM等),逐条执行程序的机器码指令。程序的执行过程涉及到指令的获取、解码、执行以及数据的读写等操作。操作系统会管理进程的资源、线程的调度和程序的运行环境,确保程序能够正确执行。
-
运行时(Runtime):程序在运行过程中可能会调用系统函数、库函数或其他外部资源,操作系统会提供相应的支持和服务,确保程序能够正常运行并与外部环境交互。
上一章说过,我们通过各种编程语言编写的代码称为源代码,源代码编写完成之后通过编译器编译之后再"链接"生成可执行文件之后才可以运行。本章主要就是讲述代码从源文件到可执行文件的需要经过的流程以及可执行文件加载到内存上的运行机制。还有对运行时堆栈内存进行说明。
上章提到计算机只能运行本地代码,对CPU来说它的母语就是机器语言,而转换成机器语言的程序就是本地代码,不论用什么语言来编写程序,转换为本地代码后,也都变成机器语言了,所以本地代码不论在任何CPU中都可以运行。
EXE文件中的代码使用的就是本地代码,所以有时我们会遇见在使用记事本打卡EXE文件后,看起来就像是一团乱码,却是可以运行的,这就是本地代码,很显然我们人类是不可能懂得本地代码的。但是我们把EXE文件的内容Dump一下(dump:把文件中的每个字节用二位十六进制数来表示)之后,他就显出了他的真面目。
计算机就是把所有信息作为数值的集合来处理的。
我们之前说过要把源代码编译之后转换为本地代码。这需要通过编译器,编译器中有源代码的对照表,再经过语法解析,句法解析,语义解析等才能生成本地代码。另外,编译器也是程序的一种也需要运行环境,但也有一种交叉编译器,可生成不同运行环境中的本地代码。
编译之后得到本地代码,但也不是可执行文件,毕竟编写程序的目的是运行,那么为了运行,在编译过后还需要进行"链接"处理,链接将生成的多个目标文件结合之后生成一个EXE文件,这个操作就是"链接"。
在链接的过程中会使用一些库和函数来简化这一过程。 注:不通过源代码而是通过库文件形式和编译器一起提供的,这样的函数称为标准函数。
Windows以提供函数的形式为应用提供了各种功能,这些函数称为API,在Windows中API 的目标文件并不存储在库文件中,而是存储在DLL文件的特殊库文件中(DLL文件是程序运行时动态结合的文件)。
可执行文件运行时的必要条件:在EXE文件中变量和函数的内存地址的是,如何表示?答案就是在EXE文件中给变量奇函数分配了虚拟的内存地址,在程序运行时,虚拟的内存地址会转换为实际的内存地址。链接器会在EXE文件的开通,追加转换内存地址所必须的信息,这个信息就是再配置信息。这个再配置信息就成为了变量和函数的相对地址。
如上图,EXE文件的内容分为再配置信息,变量组和函数组,但在加载到内存之后会额外生成两个组,就是栈和堆。栈是用来存储函数内部临时使用的变量(局部变量),以及函数调用时所用的参数和内存区域。堆是用来存储程序运行时的任意数据及对象的内存领域。两者的共同点在于都是在程序运行时申请分配的,不同的是,在内存使用方法上有些许不同,栈中对数据进行存储和清理的代码由编译器自动生成使用的内存空间自动申请和释放,但是堆得内存空间则需要根据程序员编写的程序,来明确申请分配和释放。
此外本章后还有一段问答,可以看看,本章知识很多。
标签:可执行文件,文件,EXE,代码,程序,第八章,源文件,编译器 From: https://www.cnblogs.com/wcpp/p/18032960