链接总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/i_chaoren/article/details/85388705

本文主要参考书目为《深入理解计算机系统》《程序员的自我修养》 

    链接的主要内容就是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。链接的过程主要包括了地址和空间分配、符号决议和重定位等这些步骤。 ----《程序员的自我修养》

    链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。  ----《深入理解计算机系统》

概述

    链接可以在编译时静态编译器来完成,也可以在加载时和运行时动态链接器来完成。链接器处理称为目标文件的二进制文件,它有3种不同的形式:可重定位的、可执行的和共享的。可重定位的目标文件由静态链接器合并成一个可执行的目标文件,它可以加载到内存中并执行。共享目标文件(共享库)是在运行时由动态链接器链接和加载的,或者隐含地在调用程序被加载和开始执行时,或者根据需要在程序调用dlopen库的函数时。

    链接器的两个主要任务是符号解析重定位,符号解析将目标文件中的每个全局符号都绑定到一个唯一的定义,而重定位确定每个符号的最终内存地址,并修改对那些目标的引用。

    静态链接器是由像GCC这样的编译驱动程序调用的。它们将多个可重定位目标文件合并成一个单独的可执行目标文件。多个目标文件可以定义相同的符号,而链接器用来悄悄地解析这些多重定义的规则可能在用户程序中引人微妙的错误。

    多个目标文件可以被连接到一个单独的静态库中。链接器用库来解析其他目标模块中的符号引用。许多链接器通过从左到右的顺序扫描来解析符号引用,这是另一个引起令人迷惑的链接时错误的来源。

    加载器将可执行文件的内容映射到内存,并运行这个程序。链接器还可能生成部分链接的可执行目标文件,这样的文件中有对定义在共享库中的例程和数据的未解析的引用。在加载时,加载器将部分链接的可执行文件映射到内存,然后调用动态链接器,它通过加载共享库和重定位程序中的引用来完成链接任务。

    被编译为位置无关代码的共享库可以加载到任何地方,也可以在运行时被多个进程共享。为了加载、链接和访问共享库的函数和数据,应用程序也可以在运行时使用动态链接器。

下图是根据自己的理解画的思维导图


    符号:在链接中,我们将函数变量统称为符号,函数名或变量名就是符号名。我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能正确完成的。

    符号解析。目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量(即C语言中任何以static属性声明的变量)。符号解析的目的是将每个符号引用正好和一个符号定义关联起来。

    链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。

    函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号

    根据强弱符号的定义, Linux链接器使用下面的规则来处理多重定义的符号名:

        规则1: 不允许有多个同名的强符号。

        规则2: 如果有一个强符号和多个弱符号同名,那么选择强符号。   

        规则3: 如果有多个弱符号同名, 那么从这些弱符号中任意选择一个。

    重定位。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

    重定位由两步组成:

    1)重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

    2)重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的 引用,使得它们指向正确的运行时地址。 

   可重定位目标文件:包含二进制代码和数据, 其形式可以在编译时与其他可重定位 目标文件合并起来,创建一个可执行目标文件。下图展示了一个典型的ELF可重定位目标文件的格式。

    段表:一个描述文件中各个段的数组。 

    .text: 已编译程序的机器代码。

    .data: 已初始化的全局和静态C变量。 局部C变量在运行时被保存在校中, 既不出 现在.data节中, 也不出现在.bss节中。

    .bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在 目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。

   .rodata: 只读数据, 比如printf语句中的格式串和开关语句的跳转表。

   .symtab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

    为什么要进行动态链接?

    要解决空间浪费更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接的基本思想。。

    静态库:所有的编译系统都提供一种机制, 将所有相关的目标模块打包成为一个单独的文件,称为静态库(static library),它可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块。

    静态库的缺点:1)更新困难。显式地将他们的程序与更新了的库重新链接。  2)浪费空间。代码会被复制到每个运行进程的文本段中。   

    共享库 是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker) 的程序来执行的。共享库也称为共享目标(shared object),在Linux系统中通常用.so后缀来表示。微软的操作系统大量地使用了共享库,它们称为DLL(动态链接库)。

猜你喜欢

转载自blog.csdn.net/i_chaoren/article/details/85388705