什么是链接,链接其实就是连接的意思,将所有相关的东西连接起来

简单理解静态连接和动态链接:

  • 静态链接:编译时完成链接
  • 动态链接:程序运行起来后,根据需求再去链接,这就是动态链接

一、 静态链接

1. 什么是静态链接

所谓静态链接,其实就是在编译时,调用ld/collect2链接程序,将所有的.o中的机器指令整合到一起,然后保存到可执行文件中

2. 什么时候用到静态链接呢?

编译时用到,编译时的链接就是静态链接,所以链接程序ld/collect2,也可以称为静态链接器

3. 静态链接时做了什么事

静态链接两件事

  • 符号解析
  • 重定位


4. 符号解析

接下来就大概的介绍一下这两件事,为后面的详细介绍打基础

  • 符号解析
  • 符号解析的作用

符号解析的目的就是将符号的引用(使用)和符号的定义联系起来
例子:

        a.c                    b.c

      fun(100);              void fun(int a)
                             {
                                 ...
                             }
        |                     |
        |                     |
        V                     V
       a.o  <——静态链接———>  b.o

引用符号 fun <————— 联系————> 定义符号 fun

为了方便实现符号解析,编译得到 .o 文件时,每个 .o 文件都会包含符号一张的符号表
符号表记录什么?

  • 记录本模块定义了些什么符号
  • 记录本模块引用了些什么符号

旁注:单个.c文件,也被称为一个模块,整个工程就是以模块为单位来进行组织的,模块化组织很重要,
不进行模块化在组织的话,就只能将所有内容全写到一个文件中,对于大型c程序来说,显然很难操作

但是模块化组织有一个麻烦事就是,你需要将所有的模块合成一个完整的可执行程序,这个合成的麻烦事就是由collect2/ld来承担的


5. 重定位

重定位作用:将.o文件中每个机器指令的逻辑地址,重定位为(转为)实际运行的地址

  • 如果是裸机运行的:运行的地址就是内存的物理地址
  • 如果是基于OS运行的:运行地址就是虚拟内存的地址

不过虚拟内存机制,最终还是会将虚拟地址会转为物理地址

怎么理解重定位这三个字?

  • 简单理解就是,之前的地址不对
    (每个文件模块,都有自己的起始地址,编译链接就是为了这些文件指令能在内存中按正确顺序连接起来),
    重新定位新地址,就好比导航时目的地址弄错了,重新定位一个新的目的地址,这就是“重定位”的含义

.o中的逻辑地址
逻辑地址只是理论上的,这个地址是无法被CPU取指运行的,因为逻辑地址即不是实际的物理地址,
也不是虚拟内存的虚拟地址,它只是在编译时临时给的一个编号

.o中的每个节(.text/.rodata/.data等),逻辑地址都是从0开始的

演示:查看helloworld.o的逻辑地址

但是由于.o是纯二进制文件,很难被阅读,所以需要将它反汇编,
反汇编时,每条二进制的机器指令,会被翻译为对应的每条汇编指令,是一一对应的关系

$ objdump -D helloworld.o > hw1.s
$ vim hw1.s

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   ......

Disassembly of section .rodata:

0000000000000000 <.rodata>:
   0:   64 64 64 64 64 64 64    fs fs fs fs fs fs fs fs fs fs fs fs fs fs
   7:   64 64 64 64 64 64 64
   e:   64 64 64 64 64 64 00    fs fs fs fs fs add %cl,%fs:0x65(%rax)
   ......

Disassembly of section .comment:

0000000000000000 <.comment>:
   0:   00 47 43                add    %al,0x43(%rdi)
   3:   43 3a 20                rex.XB cmp (%r8),%spl
   ......
  • 64位系统下,地址是64位的,所以十六进制的0地址有16个0
  • 我们这里只关心地址问题,有关.o文件的更多内容,我们这里不做介绍

可执行程序中的运行地址
我们这里编译出的可执行文件helloworld,是运行在Linux的虚拟内存上的,所以重定位后的运行地址是“虚拟地址”


二、 动态链接

1. 什么是动态链接?

所谓动态链接,就是在编译的时候只留下调用接口,当程序真正运行的时候,才去链接执行,
动态链接这件事不是在编译时发生的,是在程序动态运行时发生的,所以叫称为动态链接

2. 什么时候用到动态链接呢?

使用动态库时,动态库就是动态链接的

比如程序中调用printf函数,这个函数基本都是动态库提供的,程序编译后代码里面是没有printf函数代码的,
只有printf这个接口,当程序运行起来后,再去动态链接printf所在的动态库,那么程序就能调用printf函数了

3. 如何理解这里说的接口?

站在asciiC源码角度来说,这个接口就是printf函数名,但是程序被编译为二进制后,
printf就变成了一个地址,所以站在二进制的角度来说,接口就是函数第一条指令的地址

3. 动态链接的实现者是谁

动态链接由动态链接器来实现的,回顾gcc -v显示的链接信息 collect2

动态链接器

-dynamic-linker /lib64/ld-linux-x86-64.so.2 #(链接(加载)动态库)

在后面的《C函数库》讲动态库的隐式与显示加载时,会详细的介绍到这里所讲问题