在前一篇文章中:C++——g++常用命令,提及过g++编译器的编译程序的四个过程,g++可以看到程序从编译到运行的过程做了些什么。而VS等集成开发环境看不到这些。所以作为一名程序员初学者,还是很有必要静下心来学习这部分的内容的。
由于在前一篇文章没有详细介绍GCC编译器。所以就在这里介绍一下GCC编译器。主要参考:gcc和g++的区别 (很详细的描述)
简单来说,gcc与g++都是GNU(组织)的一个编译器。需要注意以下几点:
gcc main.cpp -lstdc++
C 语言的代码首先由预处理器(preprocessor)对 #include(文件包含)、#define (宏定义)与条件编译(#ifdef #ifndef #if #undef)进行处理。具体来说:
预处理之后的文件不会出现以#开头的任何语句。这就是预处理(preprocess)。
g++命令如下:
g++ -E hello.cpp -o hello.i
这一步编译,也是狭义上的编译。
编译器对预处理器的输出进行编译,生成汇编语言(assemble language)的代码。一般来说,汇编语言的代码的文件扩展名是“.s”。
编译的主要工作是进行语法检查,查看是否有语法错误。特别注意一点,对于一些非本源文件中的外部函数(非本源文件中定义的函数),外部变量(非本源文件中定义的变量),编译不会去追究其定义及实现,一般只要有对应的声明,就可编译通过,甚至说外部函数没声明都可以编译通过(比如一般gcc就不会报错,只要链接能找到对应函数,仍然会生成可执行文件,但是g++就会报函数未声明的错误)。
g++命令如下:
g++ -S hello.i -o hello.s
接着编译,汇编语言的代码由汇编器(assembler)转换为机器语言,也就是二进制文件,这个处理过程称为汇编(assemble)。汇编器的输出称为目标文件(object file)。一般来说,目标文件的扩展名是“.o”。
Linux 中,目标文件也是 ELF 文件。使用file命令来查看目标文件,会显示ELF…relocatable,据此就能够将其和可执行文件区分开。
g++命令如下:
g++ -c hello.s -o hello.o
目标文件本身还不能直接使用,无论是直接运行还是作为程序库(library)文件调用都不可以。将目标文件转换为最终可以使用的形式的处理称为链接(link)。使用程序库的情况下,会在这个阶段处理程序库的加载。
链接是将生成的一个或者多个中间文件联合生成可执行文件。所有的可执行文件都需要一个入口函数,在c语言里面入口函数是main函数,每个源文件都可能调用到其他源文件中的函数,所以生成的.o目标文件要进行链接,链接其他.o文件中的函数实现,最终生成一个可执行文件,链接主要工作是链接函数实现(本目标文件内部或者其他目标文件中定义的)和外部变量(函数体外的变量,也就是全局变量),进行各个目标文件的交互,具体是这样的,目标文件中的每个函数如果调用到了其他函数,那么就去链接其他的函数到本函数中,如果用到了外部的变量,那么去链接这个变量。
g++命令如下:
g++ hello.o -o hello
现代的 Linux 上的可执行文件,通常是指符合 ELF(Executable and Linking Format)这种特定形式的文件。 ls、 cp 这些命令(command)对应的实体文件都是可执行文件,例如/bin/ls 和 /bin/cp 等。
使用 file 命令能够查看文件是否符合 ELF 的形式。例如,要查看 /bin/ls 文件是不是ELF,在 shell 中输入如下命令即可。
$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.4.1, dynamically
linked (uses shared libs), for GNU/Linux 2.4.1, stripped
如果像这样显示 ELF……executable,就表示该文件为 ELF 的可执行文件。根据所使用的 Linux 机器的不同,可能显示 ELF 64-bit,也可能显示 ELF 32-bit MSB,这些都是ELF 的可执行文件。
ELF 文件中包含了程序(代码)以及如何运行该程序的相关信息(元数据)。程序(代码)就是机器语言(machine language)的列表。机器语言是唯一一种 CPU 能够直接执行的语言,不同种类的 CPU 使用不同的机器语言。
使用g++编译.cpp文件,直接生成可执行文件,命令如下:
g++ hello.cpp -o hello
这一部分算是对编译器编译程序的过程有了初步的了解。再接再厉。