Linux网络程序设计:基于龙芯平台
上QQ阅读APP看书,第一时间看更新

3.1.1 编译的基本概念

编译是程序开发过程中必不可少的环节,是将用高级语言(例如C、C++、Java)编写的程序转换成计算机能够理解和执行的指令的过程。在编译过程中,我们不仅可以检查程序中的错误,还能够优化代码以提高程序的执行效率。将程序编译成可执行文件的过程可以分为4个步骤:预处理、编译、汇编和链接。编译流程如图3-1所示。

图3-1 编译流程

接下来我们以将hello.c文件编译成可执行文件为例,对编译的过程进行介绍。前提是需要安装GCC,在Loongnix操作系统中,推荐直接安装“build-essential”,这一操作将一并安装编译所需的许多工具,在终端中执行以下命令即可。

sudo apt install build-essential

hello.c文件中的内容如下。

1. #include <stdio.h>
2. #define str "world."
3. int main()
4. {
5.     //这是注释
6.     printf("hello %s\n",str);
7.     return 0;
8. }

预处理是编译过程的第一步,这是一个可选步骤,可以根据需要选择执行或不执行。在预处理阶段,编译器会对程序进行预处理,包括对头文件进行解析、宏替换、条件编译等操作。预处理生成的是完整的C程序。在命令行中执行以下命令,对hello.c文件进行预处理,得到hello.i文件。

gcc -o hello.i -E hello.c

此过程中,预处理器会将包含头文件的具体内容读取到文本中来替换“#include<stdio.h>”,同时删除所有注释,将所有的“#define”删除,并展开所有的宏定义,生成hello.i文件,示例如下(左边为hello.c文件,右边为hello.i文件)。

可以看到原本的hello.c文件中只有8行代码,而现在的hello.i文件中有732行代码,并且注释被删掉了,变量“str”被替换为“world.”,并删除了“#define”。

在预处理过程中具体进行如下操作。

① 将所有的#define删除,并展开所有的宏定义。

② 处理所有的预编译指令,例如#if、#elif、#else、#endi等。

③ 处理#include预编译指令,将被包含的文件插入预编译指令的位置。

④ 添加行号信息、文件名标识,以便于调试。

⑤ 删除所有的注释。

⑥ 保留所有的#pragma编译指令,因为在编写程序的时候,我们经常要用#pragma编译指令来设定编译的状态,或指示编译器完成一些特定的动作。

⑦ 生成文件(去注释、宏替换、头文件展开),编译生成的文件不包含任何宏定义,因为宏定义已经被展开,并且包含的文件已经被插入文件。

预处理完成得到hello.i文件后,进行第二步——编译。在编译阶段,编译器将预处理后的代码翻译成汇编代码。编译器会对代码进行语法和语义分析,检查代码的正确性和合理性,并将代码转换为汇编语言的形式。在命令行中执行以下命令,对hello.i文件进行编译,得到hello.s文件。

gcc -S hello.i

hello.s文件中的内容如图3-2所示,可以看到hello.s文件中的内容全是汇编代码。

在编译中,具体操作如下。

① 进行扫描、语法分析、语义分析、源代码分析、目标代码生成、目标代码优化。通过这些操作,源代码将会被转换为目标代码。目标代码是机器可以直接执行的代码,也被称为汇编代码。

② 生成汇编代码。汇编代码是汇编器所能接受的代码形式,以机器语言的助记符形式表示,与机器语言一一对应。

图3-2 hello.s文件中的内容

③ 汇总符号。在编译过程中,全局变量和函数都有各自的符号。这些符号需要被统一汇总,以便在链接过程中进行正确的链接。

④ 生成hello.s文件。

完成编译并得到hello.s文件后,进行第三步——汇编。在命令行中执行以下命令,对hello.s文件进行汇编,得到hello.o文件,这个过程将汇编代码翻译成机器语言指令,把这些指令打包成一种被叫作可重定位目标程序的格式,并将结果保存在新生成的二进制文件hello.o中。

gcc -c hello.s

在汇编中,具体操作如下。

① 根据汇编代码和特定平台,把汇编代码翻译成二进制形式,以便计算机能够理解和执行。

② 合并各个section(操作系统或编程语言中的一种数据结构),包括代码段、数据段、BSS(未初始化数据)段等,将它们组成完整的可执行文件。

③ 合并符号,将各个源文件中的符号信息整合到同一个符号表中,以方便链接器进行下一步操作。

④ 生成hello.o文件,目标文件包含汇编代码和符号表等信息。目标文件仍然不能被直接执行,需要进行进一步的链接处理。

进行程序编译成可执行文件的最后一步——链接。在命令行中执行以下命令,对hello.o文件进行链接,得到可执行文件。处理可重定位文件,把各种符号引用和符号定义转换成可执行文件中的合适信息,通常是虚拟地址。

gcc -o hello.o

在链接中,具体操作如下。

① 合并各个hello.o文件的代码段、数据段、BSS段等section,合并符号表,进行符号解析。

② 进行符号地址重定位。链接器会根据符号表中的符号信息,将所有的符号引用和定义进行匹配,确定每个符号的最终地址,同时在目标文件中修正每个符号的引用地址。

③ 进行代码和数据的重定位。链接器会根据重定位表(relocation table)中的信息,计算出每个代码或数据的最终地址,同时在目标文件中修正每个引用地址。

④ 生成可执行文件。链接器将所有目标文件的代码段、数据段、BSS段等组合在一起,生成可执行文件。

链接操作完成后即可生成可执行文件。但在不同操作系统下,可执行文件格式并不相同。在Linux操作系统中,常见的可执行文件格式为以out为扩展名的格式或者可执行与可链接格式(Executable and Linkable Format,ELF),而ELF的可执行文件是没有扩展名的。在Windows操作系统中,常见的可执行文件格式为以exe和dll为扩展名的格式。而在macOS操作系统中,常见的可执行文件格式为Mach-O格式。需要注意的是,为特定操作系统生成的可执行文件在其他操作系统中无法执行。因此,在开发时我们需要根据目标操作系统选择合适的编译器并生成对应格式的可执行文件。