主要查看了 CSAPP
从 C 程序到可执行文件, 总共经过了这样几个部分:
1. Preprocess
在C语言中,预处理器 (C PreProcessor)是一种特殊的程序,用于在编译过程之前对源代码进行处理。预处理器会根据以 #
开头的预处理指令对源代码进行不同形式的转换,例如宏替换、文件包含等操作。
当我们使用C语言编写程序时,源代码通常包含三个部分:头文件(Header File)、函数定义和主函数。预处理器的作用就是将这些源代码文件合并起来,并生成一份纯C文件,也称为翻译单元(Translation Unit)。纯C文件中只包含了最终要编译的源代码,而没有任何预处理指令和注释。
生成纯C文件的过程,通常可以通过命令行工具 gcc -E
来执行,其中 -E
参数表示只进行预处理,不进行编译和链接。例如,假设我们有一个名为 test.c
的源文件,可以使用以下命令将其转换为纯C文件:
1 | 复制代码gcc -E test.c -o test.i |
其中,-o
参数指定了输出文件的名称为 test.i
(也可以是.c格式的),该文件即为生成的纯C文件。
生成纯C文件的主要目的是方便调试和分析源代码。由于纯C文件已经去除了预处理指令和注释,因此可以更加方便地查看和分析源代码中真正要编译的部分。此外,生成纯C文件还可以帮助我们理解预处理器的工作原理,并调试和优化预处理器宏和指令。
上图是对 hello.c 程序进行操作, 生成的纯C中间文件居然多达550行
.i 格式 和 .c 格式有什么区别
.i
格式和 .c
格式都是C语言的源文件格式,它们之间的区别在于:
- 后缀不同:
.i
是纯C文件的后缀,而.c
是包含了所有源代码和预处理指令的源文件的后缀。 - 是否经过预处理:
.i
文件是经过预处理器预处理后的纯C文件,而.c
文件则包含了所有的源代码和预处理指令。在生成*.i
文件时,预处理器会对源代码中的预处理指令进行展开并替换为相应的代码,同时还会去除注释和空行等无用信息,最终得到一个只包含有效C代码的文件。 - 用途不同:
.c
文件是编译器直接使用的源文件,其中包含了所有需要编译的代码和预处理指令。而.i
文件通常用于调试和分析源代码,它已经去除了预处理指令和无用信息,方便用户查看和分析纯C代码。
总之,.i
和 .c
文件都是C语言的源文件格式,但 .i
文件是经过预处理器处理后的纯C文件,只包含有效的C代码,而 .c
文件则包含了所有的源代码和预处理指令。.i
文件一般用于调试和分析源代码,而 .c
文件是编译器直接使用的源文件。
2. Compile
3.
链接
在计算机科学中,链接(Linking)是将多个目标文件(Object File)或库文件(Library)组合成一个可执行文件的过程。在程序开发过程中,通常会将程序代码分为多个源文件进行编写和管理,每个源文件经过编译后都会生成目标文件。然后,这些目标文件需要被链接起来才能生成最终的可执行文件。
在链接的过程中,链接器会将不同的目标文件中定义的函数、变量等符号连接起来,以便程序正确地执行。具体来说,链接器会完成以下几个主要任务:
- 符号解析(Symbol Resolution):将目标文件中使用到的未定义符号与其他目标文件中定义的符号进行匹配,并将它们连接起来。
- 重定位(Relocation):由于目标文件中的地址空间与最终可执行文件中的地址空间可能会不同,因此链接器需要对目标文件中的一些地址进行修正,以便使它们在最终可执行文件中能够正确地访问目标对象。
- 优化(Optimization):链接器还可以对目标文件中的代码进行优化,以提高最终程序的性能和效率。
在不同的操作系统和编程语言中,链接的方式和实现都可能会有所不同。在C语言中,静态链接和动态链接是两种常见的链接方式,它们使用的链接器也不同。
链接分为静态链接和动态链接两种类型。
静态链接
静态链接是指将所有目标文件和库文件直接链接到最终生成的可执行文件中的过程。在静态链接期间,编译器将目标文件中使用到的库文件中的函数和变量 直接复制 到最终生成的可执行文件中,这使得最终的可执行文件中包含了所有需要的代码和数据,可以独立地运行于系统环境中。
具体来说做了两件事:
- 空间和地址分配
- 符号解析与重定位
优点:
- 可以在不安装任何其他软件的情况下,将可执行文件直接复制到其他计算机上运行。
- 执行速度相对较快,因为所有代码和数据都已经被复制到同一个文件中。
缺点:
- 可执行文件比较大,包含了所有需要的库代码和数据,因此占用的存储空间较大。
- 如果多个程序使用同一份库文件,则会出现代码重复的情况,浪费存储空间。
- 如果库文件更新了,需要重新链接并重新编译整个程序。
动态链接
动态链接是指在程序运行时才从共享库文件 (Shared Library) 中加载所需的函数和数据的链接方式。在动态链接期间,编译器只将程序中使用到的库函数的名称记录在可执行文件中,并不将函数的实现代码复制到可执行文件中。当程序运行时,操作系统会根据程序需要从共享库文件中动态加载所需的函数和数据。
具体来说有四个步骤:
- 动态链接器自举
- 装载共享对象
- 重定位与初始化
- 转交控制权
优点:
- 可执行文件相对较小,因为它只包含了函数名等链接信息,而没有库代码和数据。
- 不同的程序可以共享同一份库文件,节省存储空间。
- 如果库文件更新了,不需要重新编译整个程序,只需要替换共享库文件即可。
缺点:
- 程序在运行时需要加载共享库文件,会增加启动时间和内存使用量。
- 执行速度相对静态链接较慢,因为需要从共享库中加载函数和数据。
总之,静态链接适合独立部署的应用程序,而动态链接适合需要共享库文件的应用程序。