从源文件到可执行文件

关于本文

这篇文章是我阅读《程序是怎样跑起来的》这本书第八章后写的读后感,也说不上读后感,就是把原文一些东西整理一下,方便日后阅读记忆。学校图书馆真是一个好地方,原本以为没有什么好的图书,后面去借阅几次发现了很多好书,这本书也是我在图书馆中借阅的。

计算机只能运行本地代码

什么是源文件和源代码,就是使用某种编程语言编写的程序就称为源代码,保存源代码的文件就是源文件。比如说以.c或者.java为扩展名的文件就是源文件。

但是源文件是无法直接运行的,这是因为CPU能直接解析并运行的不是源代码而是本地代码的程序。

本地(native),对于CPU来说就是母语的意思,任何编程语言编写的源代码最后都要翻译成本地代码,否则CPU就看不懂。

转换为本地代码后就变成了同样的语言

本地代码的内容

比如说Windows中的EXE文件里面就是本地代码。

我们使用文本工具打开exe文件可以看到类似下图的代码

用记事本打卡exe文件

这时候我们将它转变成十六进制

转换为十六进制

计算机指令也是数值的罗列,这些就是本地代码。

编译器负责转换源代码

能够把C语言等高级语言转换为本地代码的程序成为编译器,相当于现实中的翻译,每个编程语言都需要专用的编译器,C语言的就叫C编译器,这类似于现实中中文翻译成英文需要专业的英文翻译者。

编译器读入代码内容,然后通过一些对照表把源代码对照翻译成本地代码,当然不仅仅是这么简单,其中还有语法解析,句法解析,语义解析等等,最后才能生成本地代码。

根据CPU的不同,本地代码的类型也不同,所以编译器不仅和编程语言相关还和CPU相关,列入x86系列的CPU用的C编译器就和PowerPC这种CPU的C编译器不同。

还有就是编译器也是一种程序,所以也需要运行环境,那么windows用的C编译器和Linux用的C编译器就不同。还有就是交叉编译器,他生产的是和运行环境中的CPU不同的CPU所使用的的本地代码。

仅靠编译时无法得到可执行文件的

C和C++中编译之后生成的是.obj目标文件,这时候还需要链接才能得到可执行的exe文件。

比如说我们书写了一个函数

代码
代码

其中我们调用了MessageBox函数,sprintf函数。但是在我们的源代码中没有这两个函数的相关实现,比如这个文件生成的是sample.obj,那么这个文件就需要和MessageBox,sprintf的目标文件相结合(链接),才能生成完整的程序。试想一下这个sample是exe文件,那么这个文件就是不完整的,是无法执行的。

运行连接的程序就称为链接器。

启动及库文件

当我们执行上述sample.c文件的编译 链接操作的时候,我们需要指定两个文件。一个是sample.obj生成的目标文件,另一个是C0w32.obj文件,这个文件是记录着同所有程序气死位置相结合的处理内容,程序程序的启动。

后面我们还需要指定库文件,比如说32.lib cw32.lib库文件,这里面存放着sprintf函数的目标文件和MessageBox函数的目标文件(实际上是在 user32.dll文件中,这是动态链接库)

链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成EXE文件

编译链接的命令

如果不指定库文件就会发生无法识别sprintf函数和MessageBox函数的错误

编译器报错

DLL文件及导入库

Windows以函数的形式为应用提供了各种功能,这些函数被称为API(Application Programming Interface应用程序接口) 上面讲到的MessageBox()函数并不是C语言的标准库函数,而是Windows提供的API,这些API的目标文件存储在名为DLL(Dynamic Link Library)文件的特殊库文件中,它是程序运行动态结合的文件,上面我们提到MessageBox的目标文件存储在import32.lib文件中,其实这个文件只存储了两个信息——MessageBox()存储在user32.dll文件中,还有就是这个dll文件的文件夹信息。

我们把类似于import32.lib这样的库文件称为导入库。

DLL文件的好处:DLL文件中的函数可以被多个程序共用,因此借助该功能可以节约内存和磁盘。此外,在对函数的内容做修正的时候不需要重新连接(静态链接)。

另一个就是静态链接库,就是sprintf,存储着sprintf()的cw32.lib就是静态链接库。

windows中编译和链接机制

可执行文件运行时的必要条件

存储在硬盘中的exe文件运行时要读入内存中执行,在exe文件中我们会给变量和函数分配虚拟的内存地址,在程序运行的时候,这些虚拟内存地址会转换为实际内存地址,链接器会在exe文件的开头,追加转换内存地址所需的必要信息,这个信息称为再配置信息。

可以这么理解,再配置信息里存放了变量和函数的相对地址,这个相对地址就是相对于基地址的偏移量,这样各个变量的内存地址就可以用变量组的基地址加上偏移量作为地址,而这些基地址是在程序运行时被计算机分配的。

链接后的exe文件构造

程序加载时会生成堆和栈

当程序加载到内存中,出再配置信息,变量组,函数组等还会额外生成两个组,那就是堆和栈。栈是用来存储函数内部使用的临时变量以及函数调用时所用的参数的内存区域。堆是用来存储程序运行时的任意数据和对象的内存领域。

内存中的组

内存泄漏

对于栈来说,它会在函数调用的时候自动分配内存,在函数执行完毕后自动释放内存。但是对于堆来说,C语言需要程序员使用malloc申请分配,free()函数来释放,C++中需要使用new来申请分配,delete来释放,Java和C#这些语言户进行自动垃圾回收,如果我们忘记释放内存,那么这个内存空间就会一直被使用,久而久之就会造成内存泄露(memory leak)从而导致内存不足而卡死,就比如一晚上忘关水龙头,水盆中的水装满并溢出一样。

-------------本文结束感谢阅读-------------