《程序是怎样跑起来的》读书笔记——第八章 从源文件到可执行文件

1 计算机只能运行本地代码

首先,请大家看一下代码清单 8-1。这是一个用 C 语言记述的 Windows 程序。该程序运行后,会把 123 和 456 的平均值 289.5 显示在 消息框




用某种编程语言编写的程序就称为 源代码 ,保存源代码的文件称为 源文件源代码是无法直接运行的。这是因为,CPU 能直 接解析并运行的不是源代码而是本地代码的程序。

即使是用不同编程语言编写的代码,转换成本地 代码后,也都变成用同一种语言(机器语言)来表示了。

2 本地代码的内容

用记事本打开由代码清单的内容转换成本地代码得到的 EXE 文件(Sample1.exe),页面显示情况如图所示。

接下来,我们把刚才的 EXE 文件的内容 Dump 一下。 Dump 是指 把文件的内容,每个字节用 2 位十六进制数来表示的方式。

而计算机就是把所有的信息作为数值的集合来处理的。例如,A 这个字符数据就是用十六进制数 41 来表示的。与此相同,计算机指令 也是数值的罗列。这就是本地代码。

3 编译器负责转换源代码

能够把 C 语言等高级编程语言编写的源代码转换成本地代码的程 序称为 编译器

  • 编译器首先读入代码的内容,然后再把源代码转换成本地代码。
  • 读入的源代码还要经过语法解析、 句法解析、语义解析等,才能生成本地代码。

根据 CPU 类型的不同,本地代码的类型也不同。因而,编译器不仅和编程语言的种类有关,和 CPU 的类型也是相关的

  • “想要买的是何种编程语言用的编译器”
  • “编译器生成的本地代码是用于哪 种 CPU 的”
  • “该编译器是在什么环境下使用的”。

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

编译器转换源代码后,就会生成本地文件。不过,本地文件是无 法直接运行的。为了得到可以运行的 EXE 文件,编译之后还需要进行 “链接”处理。

把多个目标文件结合,生成 1 个 EXE 文件的处理就是链接,运行 连接的程序就称为 链接器 (linkage editor或连结器)

5 启动及库文件

大家可能会有这样一个疑问:“链接时不指定 sprintf() 和 MessageBox() 的目标文件也没问题么?”这个担心是多余的。在链接的 命令行末尾,存在着扩展名是“.lib”的 import32.lib 和 cw32.lib 这两个 文件。这是因为 sprintf() 的目标文件在 cw32.lib 中,MessageBox() 的目 标文件在 import32.lib 中(实际上,MessageBox() 的目标文件在 user32. dll 这个 DLL 文件中。关于这一点,我们会在后面进行说明)。

像 import32.lib 及 cw32.lib 这样的文件称为库文件库文件指的是 把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后, 就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成 EXE 文件。

6 DLL 文件及导入库

Windows 以函数的形式为应用提供了各种功能。这些形式的函数 称为 API(Application Programming Interface,应用程序接口)。

Windows 中,API 的目标文件,并不是存储在通常的库文件中,而 是存储在名为 DLL(Dynamic Link Library)文件的特殊库文件中。

我们把类似于 import32.lib 这样的库文件称为 导入库。与此相反,存储着目标文件的实体,并直接和 EXE 文件结合的库 文件形式称为 静态链接库

至此,我们总结一下 Windows 中的编译及链接机制,如图

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

EXE 文件中给变量及函数分配 了虚拟的内存地址。在程序运行时,虚拟的内存地址会转换成实际的 内存地址。链接器会在 EXE 文件的开头,追加转换内存地址所需的必 要信息。这个信息称为 再配置信息EXE 文件的再配置信息,就成为了变量和函数的相对地址。相对 地址表示的是相对于基点地址的偏移量,也就是相对距离。在源代码中,虽然变量及函数是在 不同位置分散记述的,但在链接后的 EXE 文件中,变量及函数就会变 成一个连续排列的组。这样一来,各变量的内存地址就可以用相对于 变量组起始位置这一基点的偏移量来表示,同样,各函数的内存地址 也可以用相对于函数组起始位置这一基点的偏移量来表示。

8 程序加载时会生成栈和堆

不过,当程序加载到内存后,除此之外还会额外生成两个组,那就是是用来存储函数内部临时使用的变量(局部变量),以及函数调用时所用的参数的内存区域。 是用来存储程序运行时的任意数据及对象的内存领域.

EXE 文件中并不存在栈及堆的组。栈和堆需要的内存空间是在 EXE 文件加载到内存后开始运行时得到分配的。

栈及堆的相似之处在于,他们的内存空间都是在程序运行时得到 申请分配的 .
无论是 C 语言还是 C++, 如果没有在程序中明确释放堆的内存空间,那么即使在处理完毕后, 该内存空间仍会一直残留。这个现象称为 内存泄露 (memory leak)

9 有点难度的 Q&A

Q :编译 和解释 有什么不同?

A :编译器是在运行前对所有源代码进行解释处理的。而解释器则 是在运行时对源代码的内容一行一行地进行解释处理的。

Q :“分割编译”指的是什么?

A :将整个程序分为多个源代码来编写,然后分别进行编译,最后 链接成一个 EXE 文件。这样每个源代码都相对变短,便于程序管理。

Q :“Build”指的是什么?

A :根据开发工具种类的不同,有的编译器可以通过选择“Build” 菜单来生成 EXE 文件。这种情况下,Build 指的是连续执行编译和链接。

Q :使用 DLL 文件的好处是什么?

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

Q :不链接导入库的话就无法调用 DLL 文件中的函数吗?

A :通过使用 LoadLibrary() 及 GetProcAddress() 这些 API,即使不 链接导入库,也可以在程序运行时调用 DLL 文件中的函数。不过使用 导入库更简单一些。

Q :“叠加链接”这个术语指的是什么?

A :将不会同时执行的函数,交替加载到同一个地址中运行。通过 使用“叠加链接器”这一特殊的链接器即可实现。在计算机中配置的内 存容量不多的 MS-DOS 时代,经常使用叠加链接。

Q :和内存管理相关的“垃圾回收机制”指的是什么呢?

A:垃圾回收机制(garbage collection)指的是对处理完毕后不再需 要的堆内存空间的数据和对象 B 进行清理,释放它们所使用的内存空 间。这里把不需要的数据比喻为了垃圾。进行该处理时,C 语言用的是 free() 函数,C++ 用的是 delete 运算符。在 C++ 的基础上开发出来的 Java 及 C# 这些编程语言中,程序运行环境会自动进行垃圾回收。这样 就可以避免由于程序员的疏忽(忘了记述内存的释放处理)而造成内存 泄露了。

posted @ 2017-02-12 13:55  佳佳牛  阅读(684)  评论(0编辑  收藏  举报