在连接器可操做的元素这一节中咱们提到,连接器能够操做的最小单元为目标文件,也就是说咱们见到的不管是静态库、动态库、可执行文件,都是基于目标文件构建出来的。目标文件就比如乐高积木中最小的零部件。微信
给定目标文件以及连接选项,连接器能够生成两种库,分别是静态库以及动态库,如图所示,给定一样的目标文件,连接器能够生成两种不一样类型的库,接下来咱们分别介绍。函数
静态库工具
假设这样一个应用场景,基础设计团队设计了好多实用而且功能强大的工具函数,业务团队须要用到里面的各类函数。每次新添加其中一个函数,业务团队都要去找相应的实现文件并修改连接选项。使用静态库就能够解决这个问题。静态库在Windows下是以.lib为后缀的文件,Linux下是以.a为后缀的文件。spa
为解决上述问题,基础设计团队能够提早将工具函数集合打包编译连接成为静态库提供给业务团队使用,业务团队在使用时只要连接该静态库就能够了,每次新使用一个工具函数的时候,只要该函数在此静态库中就无需进行任何修改。操作系统
你能够简单的将静态库理解为由一堆目标文件打包而成, 使用者只须要使用其中的函数而无需关注该函数来自哪一个目标文件(找到函数实现所在的目标文件是连接器来完成的,从这里也能够看出,不是全部静态库中的目标文件都会用到,而是用到哪一个连接器就连接哪一个)。静态库极大方便了对其它团队所写代码的使用。.net
静态链接设计
静态库是连接器经过静态连接将其和其它目标文件合并生成可执行文件的,以下图一所示,而静态库只不过是将多个目标文件进行了打包,在连接时只取静态库中所用到的目标文件,所以,你能够将静态连接想象成以下图2所示的过程。blog
静态库是使用库的最简单的方法,若是你想使用别人的代码,找到这些代码的静态库并简单的和你的程序连接就能够了。静态连接生成的可执行文件在运行时不依赖任何其它代码,要理解这句话,咱们须要知道静态连接下,可执行文件是如何生成的。内存
静态连接下可执行文件的生成get
在上一节中咱们知道,能够将静态连接简单的理解为连接器将使用到的目标文件集合进行拼装,拼装以后就生成了可执行文件,同时咱们在目标文件里有什么这一节中知道,目标文件分红了三段,代码段,数据段,符号表,那么在静态连接下可执行文件的生成过程如图所示:
从上图中咱们能够看到可执行文件的特色:
可执行文件和目标文件同样,也是由代码段和数据段组成。
每一个目标文件中的数据段都合并到了可执行文件的数据段,每一个目标文件当中的代码段都合并到了可执行文件的代码段。
目标文件当中的符号表并无合并到可执行文件当中,由于可执行文件不须要这些字段。
可执行文件和目标文件没有什么本质的不一样,可执行文件区别于目标文件的地方在于,可执行文件有一个入口函数,这个函数也就是咱们在C语言当中定义的main函数,main函数在执行过程当中会用到全部可执行文件当中的代码和数据。而这个main函数是被谁调用执行的呢,答案就是操做系统(Operating System),这也是后面文章当中要重点介绍的内容。
如今你应该对可执行文件有一个比较形象的认知了吧。你能够把可执行文件生成的过程想象成装订一本书,一本书中一般有好多章节,这些章节是你本身写的,且一本书不可避免的要引用其它著做。静态连接这个过程就比如不但要装订你本身写的文章,并且也把你引用的其它人的著做也直接装订进了你的书里,这里不考虑版权问题 :),这些工做完成后,只须要按一下订书器,一本书就制做完成啦。
在这个比喻中,你写的各个章节就比如你写的代码,引用的其它人的著做就比如使用其它人的静态库,装订成一本书就比如可执行文件的生成。
静态连接是使用库的最简单最直观的形式, 从静态连接生成可执行文件的过程当中能够看到,静态连接会将用到的目标文件直接合并到可执行文件当中,想象一下,若是有这样的一种静态库,几乎全部的程序都要使用到,也就是说,生成的全部可执行文件当中都有一份如出一辙的代码和数据,这将是对硬盘和内存的极大浪费,假设一个静态库为2M,那么500个可执行文件就有1G的数据是重复的。如何解决这个问题呢,答案就是使用动态库。
动态库
在前三小节中咱们了解了静态库、静态连接以及使用静态连接下可执行文件是如何生成的。接下里咱们讲解一下动态库,那么什么是动态库?
动态库(Dynamic Library),又叫共享库(Shared Library),动态连接库等,在Windows下就是咱们常见的大名鼎鼎的DLL文件了,Windows系统下大量使用了动态库。在Linux下动态库是以.so为后缀的文件,同时以lib为前缀,好比进行数字计算的动态库Math,编译连接后产生的动态库就叫作libMath.so。从名字中咱们知道动态库也是库,本质上动态库一样包含咱们已经熟悉的代码段、数据段、符号表。只不过动态库的使用方式以及使用时间和静态库不太同样。
在前面几个小节中咱们知道,使用静态库时,静态库的代码段和数据段都会直接打包copy到可执行文件当中,使用静态库无疑会增大可执行文件的大小,同时若是程序都须要某种类型的静态库,好比libc,使用静态连接的话,每一个可执行文件当中都会有一份一样的libc代码和数据的拷贝,如图所示,动态库的出现解决了此类问题。
动态库容许使用该库的可执行文件仅仅包含对动态库的引用而无需将该库拷贝到可执行文件当中。也就是说,同静态库进行总体拷贝的方式不一样,对于动态库的使用仅仅须要可执行文件当中包含必要的信息便可,为了方便理解,你能够将可执行文件当中保存的必要信息仅仅理解为须要记录动态库的名字就能够了,如图所示,同静态库相比,动态库的使用减小了可执行文件的大小。
从上面这张图中能够看出,动态库的使用解决了静态连接当中可执行文件过大的问题。咱们在前几节中将静态连接生成可执行文件的过程比做了装订一本书,静态连接将引用的其它人的著做也装订到了书里,而动态连接能够想象成做者仅仅在引用的地方写了一句话,好比引用了《码农的荒岛求生》,那么做者就在引用的地方写上“此处参考《码农的荒岛求生》”,那么读者在读到这里的时候会本身去找到码农的荒岛求生这本书并查找相应的内容,其实这个过程就是动态连接的基本思想了。
到这里咱们就能够回答以前提到过的问题了,helloworld程序中的printf函数究竟是在哪里定义的,答案就是该函数是在libc.so当中定义的,Linux下编译连接生成可执行文件时会默认动态连接libc.so(Windows下也是一样的道理),使用ldd命令就会发现每一个可执行文件都依赖libc.so。所以虽然你从没有看到过printf的定义也能够正确的使用这个函数。
接下来咱们讲解一下动态连接。
《完全理解连接器:四,库与可执行文件的生成》,欢迎关注微信公众号,码农的荒岛求生,获取更多内容。
本文分享自微信公众号 - 码农的荒岛求生(escape-it)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。