C++——gcc/g++编译的四个过程

写在前面

在前一篇文章中:C++——g++常用命令,提及过g++编译器的编译程序的四个过程,g++可以看到程序从编译到运行的过程做了些什么。而VS等集成开发环境看不到这些。所以作为一名程序员初学者,还是很有必要静下心来学习这部分的内容的。

参考文献

关于gcc与g++

由于在前一篇文章没有详细介绍GCC编译器。所以就在这里介绍一下GCC编译器。主要参考:gcc和g++的区别 (很详细的描述)

简单来说,gcc与g++都是GNU(组织)的一个编译器。需要注意以下几点:

  1. gcc与g++都可以编译c代码与c++代码。但是:后缀为.c的,gcc把它当做C程序,而g++当做是C++程序;后缀为.cpp的,两者都会认为是C++程序。
  2. 编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接。
  3. 编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接(当然可以选择手动链接,使用命令如下),所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。
gcc main.cpp -lstdc++

编译工作的四个过程

  1. 预处理,生成.i的文件
  2. 将预处理后的文件转换成汇编语言,生成.s文件
  3. 汇编变为目标代码(机器代码)生成.o的文件
  4. 连接目标代码,生成可执行程序

这里写图片描述

预处理

C 语言的代码首先由预处理器(preprocessor)对 #include(文件包含)、#define (宏定义)与条件编译(#ifdef #ifndef #if #undef)进行处理。具体来说:

  • 将被include文件中的内容复制到源文件中;
  • 然后将所有宏进行替代(根据宏的定义,替换掉所有的宏,例如定义了#define true 1,那么会将所有语句中的true用1进行代换,预处理后的文件中不再含有任何宏定义);
  • 处理条件编译的语句(只保留符合编译条件的语句,不符合条件的语句会删除掉);

预处理之后的文件不会出现以#开头的任何语句。这就是预处理(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

写在后面

这一部分算是对编译器编译程序的过程有了初步的了解。再接再厉。

猜你喜欢

转载自blog.csdn.net/zy2317878/article/details/80772803