Skip to content

Latest commit

 

History

History
206 lines (111 loc) · 15.4 KB

编译-链接-装载.md

File metadata and controls

206 lines (111 loc) · 15.4 KB

c编译&链接&装载

程序要运行起来,必须要经过四个步骤:预处理编译汇编链接

image-20220507225913654

使用 gcc 命令不跟任何的选项的话,会默认执行预处理、编译、汇编、链接这整个过程,如果程序没有错,就会得到一个可执行文件,默认为a.out。

-E选项:提示编译器执行完预处理就停下来,后边的编译、汇编、链接就先不执行了。

-S选项:提示编译器执行完编译就停下来,不去执行汇编和链接了。

-c选项:提示编译器执行完汇编就停下来。

所以,这三个选项相当于是限定了编译器执行操作的停止时间,而不是单独的将某一步拎出来执行。

1 预处理

使用-E选项,表示只进行预编译,对应生成一个 .i 文件。

  1. 将所有的“#define”删除,并且展开所有的宏定义。
  2. 处理所有的条件编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
  3. 处理“#include”预编译指令,将被包含的头文件插入到该编译指令的位置。(这个过程是递归进行的,因为被包含的文件可能还包含了其他文件)。
  4. 删除所有的注释“//”和“/* */”。
  5. 添加行号和文件名标识,方便后边编译时编译器产生调试用的行号心意以及编译时产生编译错误或警告时能够显示行号。
  6. 保留所有的#pragma编译指令,因为编译器需要使用它们。

2 编译

使用-S选项,表示编译操作执行完就结束。对应生成一个 .s 文件。

编译过程是整个程序构建的核心部分,编译成功,将高级语言指令转换为功能等效的汇编代码,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。

词法分析 词法分析是使用一种叫做lex的程序实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。产生的记号一般分为:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(运算符、等号等),然后他们放到对应的表中。

语法分析

​ 语法分析器根据用户给定的语法规则,将词法分析产生的记号序列进行解析,然后将它们构成一棵语法树。对于不同的语言,只是其语法规则不一样。用于语法分析也有一个现成的工具,叫做:yacc。

语义分析 语法分析完成了对表达式语法层面的分析,但是它不了解这个语句是否真正有意义。有的语句在语法上是合法的,但是却是没有实际的意义,比如说两个指针的做乘法运算,这个时候就需要进行语义分析,但是编译器能分析的语义也只有静态语义。

   静态语义:在编译期就可以确定的语义。通常包括声明与类型的匹配、类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含一个从浮点型到整型的转换,而语义分析就需要完成这个转换,再比如,将一个浮点型的表达式赋值给一个指针,这肯定是不行的,语义分析的时候就会发现两者类型不匹配,编译器就会报错。
   动态语义:只有在运行期才能确定的语义。比如说两个整数做除法,语法上没问题,类型也匹配,听着好像没毛病,但是,如果除数是0的话,这就有问题了,而这个问题事先是不知道的,只有在运行的时候才能发现他是有问题的,这就是动态语义。

中间代码生成 我们的代码是可以进行优化的,对于一些在编译期间就能确定的值,是会将它进行优化的,比如说上边例子中的 2+6,在编译期间就可以确定他的值为8了,但是直接在语法上进行优化的话比较困难,这时优化器会先将语法树转成中间代码。中间代码一般与目标机器和运行环境无关。(不包含数据的尺寸、变量地址和寄存器的名字等)。中间代码在不同的编译器中有着不同的形式,比较常见的有三地址码和P-代码。

   中间代码使得编译器可以分为前端和后端。编译器前端负责产生于机器无关的中间代码,编译器后端将中间代码换成机器代码。

目标代码生成与优化 代码生成器将中间代码转成机器代码,这个过程是依赖于目标机器的,因为不同的机器有着不同的字长、寄存器、数据类型等。最后目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用唯一来代替乘除法、删除出多余的指令等。

image-20220507230021715

3 汇编

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。

对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成。通常一个目标文件中至少有两个段

  1. 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

  2. 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

UNIX环境下主要有三种类型的目标文件:

  1. 可重定位文件

其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

  1. 共享的目标文件

这种文件存放了适合于在两种上下文里链接的代码和数据。

第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;

第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

  1. 可执行文件

它包含了一个可以被操作系统创建一个进程来执行之的文件。

汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

4 链接

链接的主要内容就是将各个模块之间相互引用的部分正确的衔接起来。它的工作就是把一些指令对其他符号地址的引用加以修正。

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。

例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

链接过程主要包括了地址和空间分配、符号决议和重定向。符号决议:有时候也被叫做符号绑定、名称绑定、名称决议、或者地址绑定,其实就是指用符号来去标识一个地址。重定位:重新计算各个目标的地址过程叫做重定位。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

  1. 静态链接

在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

image-20220606232002586

  1. 动态链接

image-20220606232020520

在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害

程序员的自我修养----链接

image-20220724180500890

image-20220724180551994

image-20220724173638180

Gcc的编译链接

  1. 预编译

将.c 文件转化成 .i文件

使用的gcc命令是:gcc –E

对应于预处理命令cpp

  1. 编译

将.c/.h文件转换成.s文件

使用的gcc命令是:gcc –S

对应于编译命令 cc –S

  1. 汇编

将.s 文件转化成 .o文件

使用的gcc 命令是:gcc –c

对应于汇编命令是 as

  1. 链接

将.o文件转化成可执行程序

使用的gcc 命令是: gcc

对应于链接命令是 ld

静态链接&动态链接对比(写得非常好)

(19条消息) 深入浅出静态链接和动态链接_kang___xi的博客-CSDN博客_静态链接和动态链接

Elf(Executable and Linkable Format, ELF)

Elf是什么:

  • elf文件是一种目标文件格式,用于定义不同类型目标文件以什么样的格式,都放了些什么东西。主要用于linux平台。windows下是PE/COFF格式。

  • 可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件都是以elf文件格式存储的。

  • ELF文件组成部分:文件头、段表(section)、程序头。

对象文件(Object files)有三个种类:

  1. 可重定位的对象文件(Relocatable file) 这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。

  2. 可执行的对象文件(Executable file) 这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。你应该已经知道,在我们的 Linux 系统里面,存在两种可执行的东西。除了这里说的 Executable object file,另外一种就是可执行的脚本(如shell脚本)。注意这些脚本不是 Executable object file,它们只是文本文件,但是执行这些脚本所用的解释器就是 Executable object file,比如 bash shell 程序。

  3. 可被共享的对象文件(Shared object file) 这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程 中,必须经过两个步骤:

    a) 链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。 b)在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。

image-20220724175221474

参考链接

(19条消息) ELF文件解析和加载(附代码)_木泽八的博客-CSDN博客_elf

(19条消息) ELF文件格式解析_mergerly的博客-CSDN博客_elf文件格式详解

(18条消息) 编译和链接的过程_douguailove的博客-CSDN博客_编译过程

(19条消息) C/C++程序编译过程详解_壮二宝的博客-CSDN博客_c 编译

(3条消息) 静态链接库与动态链接库----C/C++_光速跑者21的博客-CSDN博客_静态链接库

(3条消息) Linux下动态链接库的2种链接方式_执假以为真的博客-CSDN博客

(19条消息) ELF文件详解—初步认识_code&poetry的博客-CSDN博客_elf

(35条消息) ELF文件格式解析_拉斯的博客-CSDN博客