第4章 静态链接

4.1 空间和地址分配:

a.c :

extern int shared;int main()
{int a = 100;swap(&a, &shared);
}

b.c :

int shared = 1;
void swap(int* a, int* b)
{*a ^= *b ^= *a ^= *b;
}

对于链接器来说,整个链接过程,它的工作就是将几个输入的目标文件加工、合并成一个输出的可执行文件。

例如将输入的 a.o 和 b.o 文件合并成可执行文件 ab。

链接器的合并方式:

4.1.1 按序叠加:

pass
浪费空间。

4.1.2 相似段合并:

将所有输入文件的 .text 合并到输出文件的 .text段,.data段合并到 .data段,以此类推,等等。

“链接器为目标文件分配地址和空间” 这句话中的 “地址和空间” 其实有两个含义:

  1. 在输出的 可执行文件 中的空间;(链接器通过 .o文件生成可执行文件,所以可执行文件有多大、文件内容都是由链接器决定的,所以可执行文件的各个段的空间由链接器负责生成)
  2. 在装载后的 虚拟地址 中的空间;(程序在被操作系统加载后开始运行,此时为进程,操作系统为其分配虚拟地址空间,进程的虚拟地址空间中包含 .text, .data 等各个段的空间,这个空间大小也是由链接器进行赋值的)

当我们谈到空间分配时,只关注虚拟地址空间的分配。

“相似段合并”的空间分配方法采用 “两步链接”(Two-pass Linking)的方法:

  1. 第一步: 空间与地址分配;(搜集所有输入文件.o 的符号信息、段表长度,将其统一放到一个全局符号表中,给每个符号分配虚拟地址,通过每个符号的偏移量计算各个符号的虚拟地址
  2. 第二步: 符号解析与重定位。(根据第一步中搜集到的信息进行 符号解析、重定位、调整代码中的地址等)

在Linux下,ELF可执行文件默认从地址 0x08048000 开始分配(32位操作系统)。

4.2 符号解析与重定位:

4.2.2 重定位表:

链接器如何知道哪些指令需要被调整?
借助于 ELF文件中的 “重定位表”(Relocation Table)。

重定位表在ELF文件中一般是一个或多个段,例如,如果 .data段中有需要被重定位的符号,那么ELF文件中就会有一个相对应的 .rel.text段,用于保存 .data段中需要被重定位的地方。

使用 objdump -r 命令可以查看目标文件中的所有重定位入口:

objdump -r, --reloc          Display the relocation entries in the file//查看目标文件中的所有重定位入口

例如,查看 a.o 目标文件中的重定位入口信息:

[linux] objdump -r a.oa.o:     文件格式 elf64-x86-64RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000014 R_X86_64_32       shared
0000000000000021 R_X86_64_PC32     swap-0x0000000000000004RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text

查看 b.o 目标文件中的重定位入口信息:

[linux] objdump -r b.ob.o:     文件格式 elf64-x86-64RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text

可以看到 a.o 中有两个重定位入口,重定位入口 偏移(Offset) 表示该入口在要被重定位的段中的位置。

4.4 C++相关问题:

C++的一些语言特性使之必须由编译器和链接器共同支持才能完成,最主要的有两个方面:

  1. 一个是C++的重复代码消除;
  2. 还有一个是 全局构造与析构。

4.4.1 重复代码消除:

C++编译器会产生重复代码的场景: 模板、外部内联函数、虚函数表, 这些都有可能在不同的编译单元里产生相同的代码。

模板在本质上来讲很像宏(宏也是符号的替代),

例如一个模板 template class A {}; 在头文件 header.h 中,
在 a.c 中实例化 A a, 在 b.c 中实例化为 A b,这会导致在 a.o 与 b.o 的目标文件中存在重复的代码,
“当模板在一个编译单元里被实例化时,它并不知道自己是否在别的单元也被实例化了。所以当一个模板在多个编译单元同时实例化成相同的类型的时候,必然会生成重复的代码。”
如果不管这些,直接将重复的代码都保留下来,会造成下面几个问题:

  1. 空间浪费;(假设几百个编译单元同时实例化了许多个模板)
  2. 地址较易出错;
  3. 指令运行效率较低;

一种解决模板产生重复代码的方法是:

编译器 ----> 遇到template类模板或函数模板时,将每个模板的实例代码都单独存放在一个段里(目标文件中),在目标文件中以相同的规则取名 ----> 链接器 ----> 在最终的链接阶段,将同类型的模板实例的段进行合并,然后生成可执行文件。

例如有一个模板函数 add(), 某个编译单元.c文件中以 int 和float 类型实例化了该模板函数,那么该编译单元的目标文件中就包含了两个该模板实例的段,假设名为:
.temp.add , .temp.add
这样,当别编译单元也以 int 或 float 类型实例化 该模板函数后,也会生成相同的名字,
这样链接器在最终链接的时候就可以区分这些相同的模板实例段,然后将它们合并入最后的代码段。

这种做法目前被主流编译器所采用,包括 GCC 和 Visual C++。

对于 外部内联函数 和 虚函数表,消除重复代码的方法也与 模板 的做法类似:

例如对于一个有虚函数的类,有一个与之对应的虚函数表,编译器会在用到该类的多个编译单元生成虚函数表,造成代码重复,默认构造函数、默认拷贝构造函数、赋值构造运算符等也有类似的问题,解决方法与类模板一样: 编译器在 编译阶段 在目标文件.o中为虚函数表生成单独的段,链接器在 链接阶段 合并不同目标文件中的同名的段,最终生成可执行文件。

特殊情况是:
不同的编译单元使用不同的编译器,然后将生成的多个目标文件.o 使用链接器进行合并,
此时不同的编译单元中的段的名字相同,但内容由于编译器版本或者编译选项的不同,导致同一个函数编译出来的实际代码有所不同,此时:
链接器会做出一个选择,随机选取一个版本进行链接,并抛出一个警告消息。

函数链接级别:

Visual C++编译器提供一个 编译选项 叫 “函数级别链接”,针对程序和库较大时,成千上百个函数或变量,有些函数或变量可能并没有被用到,此时就没必要将其链接进来,此编译选项允许当链接器用到某个函数时,再将其合并到输出文件中,以达到节省空间的目的。缺点是会减慢编译和链接过程。

4.4.2 全局构造与析构:

一个C/C++程序 是从main开始执行,随着main函数的结束而结束。
在main函数开始调用之前,操作系统需要先初始化进程执行环境,包括:
堆内存分配初始化、线程子系统等。 C++全局对象的构造函数也在这一时期被执行。

C++全局对象 的构造函数在 main 之前被执行,C++全局对象 的析构函数在 main之后被执行。

Linux系统下一般程序的入口是 _start 函数,这个函数是Linux系统库(Glibc)的一部分。

4.4.3 C++ 与 ABI:

ABI = Application Binary Interface,与API(Application Programming Interface)类似,只是在不通层面的接口。

如果要让不同的编译器产生的目标文件能够兼容链接,要考虑它们的ABI是否兼容。

4.5 静态库链接:

程序之所以有用,因为它会有输入输出,这些输入输出的对象可以是数据,可以是人,也可以是另外一个程序,还可以是另一台计算机,一个没有输入输出的程序没有任何意义。

但是一个程序如何做到输入输出呢?
最简单的办法是使用操作系统提供的API(应用程序编程接口,Application Programming Interface)。

程序如何调用操作系统提供的API呢?
一般情况下,一种语言的开发环境虎附带有 “语言库”(Language Library),这些库就是对操作系统API的包装。
例如经典的C语言的“hello world”程序,它使用 C语言标准库 的 printf函数来输出一个字符串,在Linux下,它是对 write 系统调用的封装,在Windows下,它是对 WriteConsole 系统API的封装。

如何组织C运行库:
在一个C运行库中,包含了很多跟系统功能相关的代码,例如输入输出、文件操作、时间日期、内存管理等等,glibc中由成百上千的C语言源程序文件组成,也就是编译后会产生相同数量的目标文件。
如果把这些零散的目标文件直接提供给库的使用者,则会造成传输、管理的不便,因此,通常使用 ar 工具将这些目标进行压缩打包,生成 libc.a 静态库文件。

可以使用ar工具查看静态库文件中包含哪些目标文件:
(libc.a中共包含了大概 1400个目标文件)

ar -t libc.a...

其实一个静态库可以简单看成 一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件。

ar 压缩程序 ----> 多个 .o 文件 ----> 合成 .a 库文件

一个链接过程,就是不断的寻找程序中的符号所在的目标文件,然后将其加入进来,如果靠人工这将是一个很复杂的过程。

例如:
hello.c ----> printf.o ----> stdout.o, vprintf.o ----> …
(hello.c中包含printf()函数,在printf.o目标文件中,printf.o中又有stdout和vprintf两个符号,以此类推,找出所有的依赖的符号所在的目标文件。。)

“幸好ld链接器会处理这一切繁琐的事务,自动寻找所有需要的符号及它们所在的目标文件,将这些目标文件从 “libc.a”中 “解压”出来,最终将它们链接在一起称为一个可执行文件。”

然而仅仅是将.a库中的所有依赖目标文件找到并链接仍是不够的,后面再继续介绍。

collect2:

collect2 可以看作是ld链接器的一个包装,它会调用ld链接器来完成对目标文件的链接,然后再对链接结果进行一些处理,主要是收集所有与程序初始化相关的信息并且构造初始化的结构。

Q&A:

Q: 为什么静态库里面一个目标文件只包含一个函数?
A: 链接器在链接静态库的时候是以目标文件为单位的。当引用了静态库中的printf()函数时,链接器就会把printf()函数所在的目标文件链接进来。
如果很多函数都放在同一个目标文件中,就可能会造成其他没用的函数都被一起链接进了输出结果中。
由于运行库有成百上千个函数,数量非常庞大,每个函数独立的放在一个目标文件中可以尽量减少空间的浪费,那些没有被用到的目标文件(函数)就不要链接到最终的输出文件中。


9. C/C++ 运行库、静态库、动态库 的区别与联系:

https://blog.csdn.net/ithzhang/article/details/20160009

9.1 从C和C++运行库说起:

为了提高C语言的开发效率,C标准定义了一系列常用的函数,称为C库函数。
C标准仅仅定义了函数原型,并没有提供实现。因此这个任务留给了各个支持C语言标准的编译器。

每个编译器通常实现了标准C的超集,称为 “C运行时库”(C Run Time Library),简称 CRT。

对于VC++编译器来说,它提供的CRT库支持C标准定义的标准C函数,同是也有一些专门针对Windows系统特别设计的函数(对于Linux系统、GCC编译器也是一样的道理)。

可以简单理解:
C标准库 + 编译器自定义的针对具体操作提供的专门函数 = C运行时库(CRT)

与C语言类似,C++也定义了自己的标准,同时提供相关支持库,我们将它称为C++运行时库或C++标准库。

由于C++对C的兼容性,C++标准库包括了C标准库,除此之外还包括 IO流标准模板库STL

9.2 VC++在何处实现C和C++运行库:

VC++完美支持C和C++标准,即按照C和C++的标准中定义的函数原型实现了上述运行时库。

为了方便有不同需求的用户的使用,VC++分别实现了 动态链接库DLL版本 和 静态库LIB版本。
同时为了支持程序调试且不影响程序的性能,又分别提供了对应的调试版本。

对于C运行时库 CRT、VC6.0、VC2005、VC2008、VC2010等,均提供了 DLL版本 和 LIB版本(供用户根据实际需要选择DLL方式还是LIB方式的库)。

9.3 动态版(DLL)和静态版(LIB)C和C++运行库的优缺点:

动态链接库: DLL(Dynamic Linking Library),Windows下的 .dll 和 Linux下的 .so 文件;
静态链接库: Static Linking Library, Windows下的 .lib 和 Linux下的 .a 文件。

因为静态版必须把C和C++运行库复制到目标程序(.o)中,所以产生的可执行文件会比较大。

使用DLL版的C和C++运行库,程序砸运行时动态的加载对应的DLL,程序体积变小,但一个很大的问题就是一旦找不到对应的DLL,程序将无法运行。

静态库的链接方法:

gcc main.c -static -o main -L. -lstatic

动态库的链接方法:

gcc main.c -o main -L. -lshared

《程序员的自我修养》第4章---静态链接相关推荐

  1. 动态链接1 程序员的自我修养第七章笔记

    0. 序 预计这篇文章会很长,所以我打算把它拆成几篇文章来写.折腾了挺长时间才差不多捋清楚,主要书上一些地方和现在的实现有一点出入. 1. 概览 与动态链接有关系的节大概有这样一些,.interp, ...

  2. 《程序员的自我修养》读书笔记——动态链接

    之前介绍过静态链接,动态链接相对于静态链接稍微要麻烦一些.总体来说,两者的过程都复杂,步骤太多,涉及到重定位,符号修正,地址修正等等.--复杂 动态链接 静态链接在计算机早期还是比较流行的,但是到了后 ...

  3. 《程序员的自我修养》

    <程序员的自我修养>这本书偏底层,来来回回读了有三四遍了,每一次都有新的收获,不过很快又会忘记,所以写下了这本书从17年12月份至今的全书的笔记,留作以后自己复习. 第二章:编译和链接 源 ...

  4. 腾讯朋友力荐书籍:程序员的自我修养:链接、装载与库

    后台开发需要学习底层知识,只有底层知识掌握了,学一些中间件是信手捏来,中间件也是跑在底层的操作系统上.<<程序员的自我修养:链接.装载与库>>对学习底层知识非常有帮助,腾讯的朋 ...

  5. 读书笔记程序员的自我修养 0

    读书笔记<<程序员的自我修养>> 0 为什么要读这本书? 可能因为自己是读硬件的缘故,对于编程,我总是尝试的了解各种表象的下面发生了什么事情.而困扰了我的许多问题,在这本书上都 ...

  6. 程序员的自我修养读书笔记-1

    前序:作为一个马上就要工作的非科班本科生,前段时间为了找工作,有针对性的学习了一些编程语言,数据结构,网络方面的知识,学的非常浅,非常杂乱,存粹是为了应对找工作.现在空下来了,想着以后应该就是走程序员 ...

  7. 程序员的自我修养------勘误表

    谢谢你们的辛勤劳动,[程序员的自我修养]真的不错,花一周时间看完后,把以前的东西都串起来了,在看的过程中,发现一些小瑕疵,看到顺便记下,要在以后的版本中修改就更完美了.我购买的是2012年5月第9次印 ...

  8. 《程序员的自我修养》读书总结

    http://www.jianshu.com/p/47156b4259ed 最初买<程序员的自我修养>这本书,只因为在京东买书差一些钱,不够用优惠券.买回来以后的很长一段时间,我都以为这本 ...

  9. 《程序员的自我修养》导读

    大家好,我是Cone,一名毕业于双非本科的抖音全栈程序猿. 今天来和大家分享<程序员的自我修养----链接.装载与库>这本书的全书导读经验,它在去年我拿下微信.抖音.百度等大厂sp及以上o ...

  10. 《程序员的自我修养—链接、装载与库》pdf书签,目录分享

    在网上下载到<程序员的自我修养-链接.装载与库>pdf版本,拜读之后受益匪浅,但是因为下载的pdf没有书签,所以想要查找某一章的内容不是很方便,于是自己制作了一下书签文件,将书签文件导入p ...

最新文章

  1. 如何选择漏电保护器规格型号_家用漏电开关型号介绍 如何选用家用漏电开关...
  2. Java Post 数据请求和接收
  3. python 一维数组所有元素是否大于_如何最好在python中将一维数组连续元素分组...
  4. 【数据库】pymysql数据库事务操作
  5. ZOJ 1004 Anagrams by Stack(DFS+数据结构)
  6. Linux系统编程:使用mutex互斥锁和条件变量实现多个生成者和消费者模型
  7. C语言的EOF是什么?getchar()!=EOF返回的是什么?
  8. 计算机语言php自学,php自学需要多久?
  9. [TCP/IP] TCP建立与终止
  10. 机顶盒天线接头怎么接_户户通天线怎么安装图解
  11. Linux配置jdk环境变量(详细版)
  12. 转:施炜:铁军组织是怎样炼成的?高能组织=人×管理体系×数字标准
  13. jspsmartupload简述
  14. 计算机 未保存,电脑突然关机wps没保存怎么办
  15. 加息对银行股影响|加息是对银行股的利好
  16. Linux电池电量信息读取,linux内核 – 如何在Linux内核模块中获取电池电量?
  17. 港大火星实验室最新工作:用于精确实时3D SLAM的高效概率自适应体素地图
  18. linux atop日志查看,A - atop - 监控Linux系统资源与进程的工具 - 《Linux命令大全搜索工具(旧版)》 - 书栈网 · BookStack...
  19. 【原创】harvey指导soc裸机程序文件头等制作_Detective_ALong_新浪博客
  20. VM虚拟机无法安装WIN7系统,出现“start booting from cd”

热门文章

  1. 震撼的奥运会开幕式BOB版及NBC版下载
  2. 一文读懂Toast显示流程
  3. 浙江高考平行志愿录取过程模拟(Delphi实现)
  4. outlook邮件恢复字体_如何更改Outlook 2013中邮件列表中使用的字体大小
  5. tomcat8弱口令漏洞复现与getshell
  6. pc项目,通过左侧导航树展示右侧router
  7. ubuntu By not providing “Findcatkin.cmake“ in CMAKE_MODULE_PATH this project has asked CMake to fi
  8. 基于Springboot+Netty实现Web聊天室
  9. JavaScript笔记之二--- 崔西凡day03
  10. linux管道简单理解