链接的相关知识

链接

模块化编程与链接

有了汇编语言以后,生产力大大提高,随之而来的是软件规模的规模也开始日渐庞大,这时程序的代码量也已经开始快速膨胀,导致人们要开始考虑将不同的功能的代码以一定的方式组织起来,使得更加容易阅读和理解,以便于日后修改和重复使用。

人们开始以不同的功能或性质进行划分为不同的模块,不同模块之间按照层级的结构或其他结构来组织。

现代的软件开发过程中,软件的规模往往拥有成千上万个模块,这些模块之间相互依赖,又相互独立。这种按层次化及模块化存储和组织源代码有很好处,比如代码可以更加容易阅读、理解、重用,每个代码可以单独开发、编译、测试,改变代码部分不需要编译整个程序等。

模块之间如何通信(关联)

在一个程序被分割成多个模块以后,那么这些模块之间最后如何组合形成一个单一的程序是必须解决的问题,模块之间如何组合的问题可以归结于模块之间如何通信的问题,最常见的问题属于静态语言C/C++模块之间通信的两种方式

  • 模块间的函数调用

函数访问需要知道函数的目标函数的地址

  • 模块间的变量引用(访问)

变量访问需要也需知道目标变量的地址

可见其通信方式可归集于一种,那就是模块符号的引用

  • 模块间依靠符号来通信类似拼图版,定义符号的模块多出一块区域,引用该符号的模块刚好少了那一块区域,两者拼接刚好完美组合。
  • 模块拼接的过程即是链接

在这里插入图片描述

什么是链接器
链接的接口–》符号 定义和声明的再次理解

链接过程的本质就是要把多个不同的目标文件之间相互"沾"到一起

在链接过程中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。

比如目标文件B要用到目标文件中A的函数"foo",那么我们称目标文件A定义(Define)了函数“foo”,称目标文件B引用(Reference)了目标文件A中的函数“foo”。这两个概念同样适用于变量

每个函数或变量都有自己独特的名字,才能避免链接过程中不同的变量和函数之间的混淆,在链接中,我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名。

符号的管理

我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能够正确完成。链接过程中很

链接的任务

链接的两个基本任务

  • 符号解析
    一个程序会定义和引用符号,汇编器(as)会将符号定义存储在目标文件中(形成符号表),每个符号对应一个结构数组,包含了该有关该符号的信息。
    而符号解析就是在链接器链接过程中,链接器将每个符号引用与一个符号定义相关联(此时,链接器必须决定将其中的某个定义用于所有后续引用),一旦链接器和一个独一无二的的目标建立联系,每个引用都会有一个唯一的符号定义,所以就存在一种情况,多个模块中存在多个符号定义,就有相应的强弱符号规则(如果只有单个模块,编译器当然能确定出来,但是当多个模块时,分离编译,编译器将责任推给了链接器)

  • 重定位(根据as形成的重定位表对符号进行重定位,而这些重定位条目是链接器的操作,链接器会盲目地浏览这些重定位条目,然后按照他所说的去做,结果就是,将这些所有引用已使用的有效绝对地址进行修补)
    在重定位期间,它会将所有模块合并在一起,变为单个可执行程序,器就可以直接在系统上加载和执行,所以其在合并不同的模块时,其必须弄清楚每个符号,每个函数和每个变量是要准备存储在哪,这就是重定位(确定虚拟内存地址,绝对地址),然后将该地址绑定到所有的符号引用。(可以看出,符号解析和重定位一同在起作用)
    因为最初的符号(函数、数据)只是存储在其目标模块中的某个偏移处。

避免使用全局变量

由于多模块符号链接器存在奇怪的强弱规则,当存在多个全局定义是,可能会出现奇怪的问题,所以一般情况下避免使用全局变量,所以在使用全局变量时,请看看是否可以将其声明为static,而且如果定义了全局符号,就将其初始化了
而如果你想要引用其他模块的外部变量,则通过使用extern属性来告诉编译器

PS:

  1. .o、 .so 、a.out的文件格式都是ELF格式,所以它们的二进制文件结构类似
    在这里插入图片描述

  2. 可执行程序加载到内存的代码段和可执行程序ELF组织的代码段不同,加载到内存的代码段包含了.text、.rodata、.init等可执行程序只读段的集合
    见图:
    在这里插入图片描述

  • brk是一个全局变量,由内核维护
链接的优势
  • 链接的一个真正优势是允许我们创建库

PS:作为一名程序员,我们总是希望创建抽象,然后将这些抽象呈现给用户。如:我们通过创建定义API,实现打包这些API并将其提供给其他程序员使用。

库的引入
  • 库能使得只需要链接实际引用的.o文件
  • 我们可以随时重新构建该档案库,当其中某个功能改变了,只需要重新编译相应的文件(一个文件对应一个功能),然后重新存档所有.o文件
在使用静态库时需要注意的问题

由于链接器在使用静态库时,按下面步骤进行,所以在使用时需要注意

  1. 它在命令行上按顺序扫描所有的.o文件和.a文件,所以我们应该按照某种顺序来写 .o 和.a文件列表
  2. 在扫描过程中饭,其保留了当前未定义(解析)的引用列表
  3. 当遇到每个新的.o或.a文件obj时,其会尝试根据obj中定义的符号解析列表来进行解析引用。
  4. 最终,如果扫描结束时,列表还存在有任何条目,那么就会出错(undefine reference to xxxx)
  • 关键就是链接器将尝试按照命令行从左向右解析这些引用,所以,对于我们,我们需要注意,如果文件放在命令行上的顺序上有所不同,你可能将遇到一些奇怪的令人困惑的链接器错误

在这里插入图片描述
若先扫描libtest.o时,链接器发现其缺少libfun的定义,就会将其放到待解析条目,然后当扫描到mine.a时就会找到该条目的引用解析,而如果反过来书写,那么将永远找不到,就会报错

静态库与动态库

  • 库文件:某些功能代码的集合
  • 库文件中不允许有main函数,其只是功能代码
  • 在应用程序需要连接外部库的情况下,linux默认对库的连接是使用动态库,在找不到动态库的情况下再选择静态库,在-l时,静态库需要放到最后
  • 静态库==》代码合到模块中,无零散文件
  • 动态库—》代码在单独的文件中,有零散文件
  • 静态库也不是一事无成,对于一个稳定的程序,其就应该使用静态库
动态链接

引出:

  • 要解决空浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件

  • 而不是将它们静态地链接在一起。

  • 简单地讲,就是不对那些组成程序的目标文件进行链接

  • 等到程序要运行时才进行链接

  • 也就是将链接这个过程推迟到了运行时再进行,这就是动态链接的基本思想

​ 还是以Program1和Program2为例,假设我们保留Program1.o、Program2.o、lib.o这三个目标文件。当我们要运行Program1.当系统发现Program1.o运行时需要Lib.o.即Program1.o依赖于Lib.o(应该是运行时依赖),那么系统接着加载Lib.o,如果Program1.o或Lib.o还依赖于其他目标文件,系统会按照这种方法将它们全部加载到内存,当加载完成之后,系统才开始进行链接工作,而此时的链接工作和静态链接类似(符号解析。符号重定位等),然后系统开始把控制器交给Program1.o的程序入口处。程序开始运行。

​ 而如果此时我们需要运行Program2.o,那么系统只需要加载Program2.o,而不需要加载Lib.o,因为此时内存已经存在了一份Lib.o的副本

  • 可以看出动态链接,只是将链接过程推迟到了加载到"内存"后,运行前,应该运行时一定需要符号地址

  • 解决了共享目标文件多个副本浪费磁盘和内存空间的问题,磁盘和内存中只存在一份

  • 另外在内存中共享一个目标文件模块不仅仅是节省内存,还可以减少物理页面的换入换出,相应的增加了CPU缓存的命中率(因为不同进程间的数据和指令访问都集中在了同一个模块上)

    理论上讲程序的升级和维护,只需要将旧的文件覆盖掉,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就自动完成了升级的目标(耦合度)

疑问:

此时的链接器和静态时的链接器还是同一个吗?

兼容性

动态链接还那可以增加程序的兼容性。一个程序在不同的平台运行时可以动态地链接到由操作系统提供的动态链接库,这些动态链接库相当于在程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间依赖的差异性。(语言级别?)

如一printf,可能不同的操作系统对于其的实现机制不同,如果我们的程序是静态链接,那么程序需要分配链接成多个版本并且分别发布;但是如果是动态链接,那么多个操作系统只需要一个版本,就可以在两个操作系统上运行,动态地选择相应的printf()的实现版本。

动态链接存在的问题

动态链接并不是一种万能膏药,动态链接也有诸多问题及令人烦恼和费解的地方,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致原有的程序无法运行。(缺少了一种有效的动态库版本管理机制,使得用户在新程序安装完成之后,其它某个程序无法正常运行的现象–》DLL HELL)

动态链接的基本实现
  • 动态链接的基本思想

    • 分解思维,一个文件,我们不希望它的各个模块是绑定在一起,作为一个整体,即将各个模块拆分出来,成为各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个单独的可执行文件。–》这也是模块化编程的好处,一个程序被分成了若干文件,有程序的主要部分和程序所依赖的共享对象,而静态链接,最终整个程序只有一个可执行文件
    • 低耦合思维、接口思维、代理思维,动态链接可以作为中间层,使得程序在不同的操作系统下都能很好的执行
  • 动态链接文件与目标文件是有区别的

在LINUX系统中,ELF动态链接文件被称为动态共享对象DSO(dynamical Linking Obeject),简称共享对象,它们一般以.so为扩展名的一些文件;

而在Windows系统中,动态链接文件被称为动态连接库(Dynamical Linking Libary),它们通常就是我们平时常见的.dll为扩展名的文件。

从本质上讲,普通可执行程序和动态链接库中都包含指令和数据

在使用动态链接库的情况下,程序本身被分为了程序主要模块(Program1.o)和动态链接模块(Lib.so),但实际上它们可以看作是整个程序的一个模块。所以当我们提到程序模块时,可以指程序主模块也可以指动态链接库,如一辆车,由轮子,车壳,发动机等组成,它们分别都是它的一个模块

在LINUX中,常见的C语言库的运行库glibc,它的动态库链接形式的版本保存在"/lib"目录下,文件名叫做"libc.so"。

整个系统只保留了一份C语言库的动态链接文件"libc.so",而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序装载时,系统的动态链接器会将程序所需要的所有动态链接库(DSO)(最基本的就是lib.so)装载到进程的地址空间,并且将所有未决的符号绑定到相应的动态链接库中,并进行重定位工作。

动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持

  • 动态链接情况下,进程的虚拟地址空间的分布与静态链接不同
  • 存储管理、内存共享、进程线程等机制在动态链接下也会有一宿微妙的变化。(现在没看出来)
动态链接程序运行时的地址空间分布

查看程序的地址空间分布的办法

  1. 找到程序运行时的进程号
  2. cat /proc/进程号/maps —》查看对应进程地址空间分布

在这里插入图片描述

  • 可以看到,整个进程虚拟地址空间中,多出了几个文件的映射。Lib.so与Program1一样,它们都是被整个操作系统用同样的方法映射至进程的虚拟地址空间,只是占据的虚拟地址和长度不同 (代码段、动态链接库段)。
  • 还有一个重要的映射共享对象就是ld-2.6.so,他实际上是LINUX下的动态链接器。动态链接器与普通共享对象一样被映射到了进程的地址空间。
    • 在系统开始运行Program1之前,首先会把控制权交给动态链接器(而不是直接交给程序),由它完成所有的动态链接工作以后再把控制权交给Program1,然后开始执行。
  • 动态链接模块的装载利用readelf -l Lib.so查看其装载属性,可看出除了文件类型与普通程序不同以外,其他几乎与普通程序一样,但还有一点不同,就是装载地址是从地址0x00000000开始的。而动态链接库已经算是一个可执行程序了,为什么它的装载地址会是址0x00000000,一个无效地址呢?而如上图,Lib.so的最终装载地址并不是0x00000000,因此可以推断共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享对象。
动态链接器

还记得我前面提到的一个疑问吗?此时的链接器和静态链接器ld是同一个吗?

很明显,不是同一个,程序与libc.so之间真正的链接工作是由动态链接器完成的,而不是由我们前面看到过的静态链接器ld完成的。

动态链接是把链接这个过程从本来的程序装载前被推迟到了装载的时候。

那么有存在一个问题,动态链接将链接过程推迟到了程序装载的时候,那么每次运行程序进行装载,那不就都需要进行重新链接吗?而且此时和局部性原理可没有丝毫关系啊,那它的性能影响不是极大吗?

  • 的确,动态链接会导致程序在性能的有一些损失,但是对动态链接的链接过程可以进行优化—》延迟绑定?啊延迟绑定不是每次都还需要进行链接呢?后面看吧,书上说其可以使得动态链接的性能损失尽可能地减少。
形成静态库
gcc -c *.c---->*.o
ar crv libxxx.a *.o
  • 静态库是功能代码编译完成后的中间文件的集合(相当于将这些.o文件打了个包)
  • 静态库在链接阶段中内容合并到最终的可执行文件中。执行时,不需要静态库的支持。
使用静态库
  • -L(库的路径)
  • -l(库名称)
  • 由于静态库链接时即合并了,当发行时不需要再为用户提供静态库
main.c

gcc -o main mian.c -L . -lxxx
形成动态库
  • 链接阶段仅仅是让可执行文件知道所用功能代码在哪个库中。执行时,由操作系统将动态库单独动态加载到内存上执行
//不需要生成中间文件
gcc -share -fPIC -o libxxxx.so *.c
  • 可见为什么有时候称动态链接对象为不可执行的执行文件,因为我们无法直接执行它,当然有特殊方法,而是交由操作系统来进行加载。

在这里插入图片描述

此时的Linker,Lib.so没有链接进来,链接的输入目标文件只有Program1.o(当然还有C语言运行时库,我们这里占时忽略),但我们在使用动态库时不是-l指定了动态库吗,这是怎么回事呢?

当ld对Program1进行链接时,当发现其性质为动态共享对象的函数符号,那么链接器就会将这个符号的引用进行标记为一个动态链接符号(Stub,此时Lib.so包含了完整的符号信息—》ldd此时刚刚好能找到该标记,即显示出对应需要的DOS),不对其符号进行重定位,而把这个过程留给装载时再进行,对于可重定位符号,正常进行链接。

使用动态库
  • -L(库的路径)
  • -l(库名称)
  • 使用与静态库一样
main.c

gcc -o main mian.c -L . -lxxx

eclipse使用时引入头文件还需要设置,才能找到头文件,或者直接设置环境变量CPLUS_INCLUDE_PATH

在这里插入图片描述

再次进行编译,发现
在这里插入图片描述
这是正常现象,因为没有对库进行链接,此时ld的符号是UND,若没有其他的输入模块,当然是undefined

在这里插入图片描述

此时编译成功生成.exe文件,但是由于是动态链接,所以此时还无法使用,还需要配置环境变量,因为此时程序的装载需要操作系统将其依赖的共享库加载到内存,而操作系统搜索的地方是有限的(/lib , /usr/lib 和ld.so.conf 指定的目录)

在这里插入图片描述

  • loading share libaray 说的多清楚啊

在这里插入图片描述

LINUX中ldconfig
  • ldconfig是一个动态链接库管理命令,其目的为了让动态链接库为系统所共享

  • 程序引用动态库时,操作系统动态加载库文件到内存上来执行

    • 操作系统默认搜索/lib和usr/lib,以及配置文件/etc/ld.so.conf内所列举的目录下的库文件
  • ldconfig执行时会去搜索以上的库文件,进而创建出动态装入 连接和缓存文件

    • 缓存文件默认为/etc/ld.so.cache,该文件保存已排好序的动态链接库名字列表
  • ldconfig通常在系统启动时运行,而当用户安装一个新的动态库时,就需要手工运行这个命令

如果动态链接器在每次查找每一个共享库都要去遍历这些目录,那将会非常耗费时间。所以LINUX系统中都有一个叫做ldconfig的程序,这个程序的作用是为共享库目录下的各个共享库创建、删除或更新相应的SO-NAME(即相应的符号链接),并且该程序还会降这些SO-NAME收集起来,集中存放到/etc/ld.so.cache文件里面,并创建一个SO-NAME的缓存。当动态链接器要寻找共享库时,他可以直接从/etc/ld.so.cache里面查找。而/etc/so.cache的结构是经过特殊处理的,非常适合查找,所以这个设计大大加快了查找过程。

ldconfig的参数
ldconfig命令参数说明:
1. -v(--verbose):用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字.

2、-n :用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.

3、-N :此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.

4、-X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.

5、-f CONF : 此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf.

6、-C CACHE :此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,此文件存放已排好序的可共享的动态链接库的列表.

7、-r ROOT :此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.如用-r/usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.

8、-l :通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用此项.

9、-p或--print-cache :此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.

10、-c FORMAT 或--format=FORMAT :此选项用于指定缓存文件所使用的格式,共有三种:ld(老格式),new(新格式)和compat(兼容格式,此为默认格式).

11、-V : 此选项打印出ldconfig的版本信息,而后退出.

12、- 或 --help 或--usage : 这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出.、
ldconfig的注意事项

1、往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf文件的,但是添加完后需要调用下ldconfig,不然添加的library会找不到。

2、如果添加的library不在/lib和/usr/lib里面的话,就一定要修改/etc/ld.so.conf文件,往该文件追加library所在的路径,然后也需要重新调用下ldconfig命令。比如在安装MySQL的时候,其库文件/usr/local/mysql/lib,就需要追加到/etc/ld.so.conf文件中。命令如下:

# echo "/usr/local/mysql/lib" >> /etc/ld.so.conf

# ldconfig -v | grep mysql

3、如果添加的library不在/lib或/usr/lib下,但是却没有权限操作写/etc/ld.so.conf文件的话,这时就需要往export里写一个全局变量LD_LIBRARY_PATH,就可以了。

  • 由于更改环境变量,那么就可以修改.bashrc来使得环境变量永久生效
同时使用静态库和动态库

在应用程序需要连接外部库的情况下,linux默认对库的连接是使用动态库,在找不到动态库的情况下再选择静态库。使用方式为:

gcc test.cpp -L. -ltestlib

如果当前目录有两个库libtestlib.so libtestlib.a 则肯定是连接libtestlib.so。如果要指定为连接静态库则使用:

gcc test.cpp -L. -static -ltestlib

使用静态库进行连接。

当对动态库与静态库混合连接的时候,使用-static会导致所有的库都使用静态连接的方式。这时需要作用-Wl的方式:

gcc test.cpp -L. -Wl,-Bstatic -ltestlib  -Wl,-Bdynamic -ltestdll 

另外还要注意系统的运行库使用动态连接的方式,所以当动态库在静态库前面连接时,必须在命令行最后使用动态连接的命令才能正常连接

,如:

gcc test.cpp -L. -Wl,-Bdynamic -ltestdll -Wl,-Bstatic -ltestlib -Wl,-Bdynamic

最后的-Wl,-Bdynamic表示将缺省库链接模式恢复成动态链接。

二:查看静态库导出函数

注意:参数信息只能存在于 .h 头文件中
windows下
dumpbin /exports libxxx.a
linux 下
nm -g --defined-only libxxx.a

场景是这样的。我在写一个Nginx模块,该模块使用了MySQL的C客户端接口库libmysqlclient,当然mysqlclient还引用了其他的库,比如libm, libz, libcrypto等等。对于使用mysqlclient的代码来说,需要关心的只是mysqlclient引用到的动态库。大部分情况下,不是每台机器都安装有libmysqlclient,所以我想把这个库静态链接到Nginx模块中,但又不想把mysqlclient引用的其他库也静态的链接进来。
  我们知道gcc的-static选项可以使链接器执行静态链接。但简单地使用-static显得有些’暴力’,因为他会把命令行中-static后面的所有-l指明的库都静态链接,更主要的是,有些库可能并没有提供静态库(.a),而只提供了动态库(.so)。这样的话,使用-static就会造成链接错误。
  
之前的链接选项大致是这样的,

1CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

修改过是这样的,

12CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -Wl,-Bstatic -lmysqlclient**** -Wl,-Bdynamic -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

其中用到的两个选项:-Wl,-Bstatic和-Wl,-Bdynamic。这两个选项是gcc的特殊选项,它会将选项的参数传递给链接器,作为链接器的选项。比如-Wl,-Bstatic告诉链接器使用-Bstatic选项,该选项是告诉链接器,对接下来的-l选项使用静态链接;-Wl,-Bdynamic就是告诉链接器对接下来的-l选项使用动态链接。下面是man gcc对-Wl,option的描述,

-Wl,option Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. You can use this syntax to pass an argument to the option. For example, -Wl,-Map,output.map passes -Map output.map to the linker. When using the GNU linker, you can also get the same effect with -Wl,-Map=output.map.

下面是man ld分别对-Bstatic和-Bdynamic的描述,

-Bdynamic -dy -call_shared Link against dynamic libraries. You may use this option multiple times on the command line: it affects library searching for -l options which follow it. -Bstatic -dn -non_shared -static Do not link against shared libraries. You may use this option multiple times on the command line: it affects library searching for -l options which follow it. This option also implies --unresolved-symbols=report-all. This option can be used with -shared. Doing so means that a shared library is being created but that all of the library’s external references must be resolved by pulling in entries from static libraries

Linux编译动态链接库so避免运行时才发现函数未定义符号的错误undefined symbol的ld参数

ldd 查看 elf文件依赖的 so 动态链接库 可以 export LD_LIBRARY_PATH=/path 设置 so文件的路径,
nm -u *.so 或者 nm |grep U 查看 那些在 动态链接库中的符号。

“U” The symbol is undefinedundefined的 symbol 这种就是表示 在其他 so动态链接库里面定义的。但是如果你的编译的 是so文件,如果符号不在外部任何so文件里面,默认的配置也不会提示错误。而是编译通过。那个自己忘了定义的符号也在 这 undefined symbol里面,但是运行时就加载不成功了。

   文档说,这种编译so动态链接库时找不到符号(不在任何外部so文件里面,自己的程序也没有定义)也允许编译通过是有原因的,参见 ld 的man 说明   --allow-shlib-undefined 解释(好像英文版的才完整,中文的man ld不完整 可以直接查看网页 https://sourceware.org/binutils/docs-2.24/ld/Options.html#Options )。就是让你链接时用的一个版本的so,运行时加载用的另外一个版本的so,可能你的加载时的so里面有这个符号,所以就先让你找不到符号也编译通过了。如果是编译exe,这中链接时找不到定义的符号的就直接给你报错了。 so动态链接库就不会报错。其实这种特性应该是比较少用,最好在so链接是碰到这个未找到的符号也是报错的好。
所以我觉得编译的动态链接库的时候最好加上 --unresolved-symbols=ignore-in-shared-libs  或者  --no-undefined 来检查一下。这样如果是自己的疏忽在 .c 源文件里面忘记的 某函数的定义,,编译的时候就可以提示错误了。

这里有3个参数可以使用–undefined symbols 和 --no-allow-shlib-undefined 参数的作用范围不一样而已,–undefined symbols 针对常规object文件,–no-allow-shlib-undefined针对的是符号在外部的未定义的shared object里面。–unresolved-symbols和–undefined symbols 作用差不多,不过更具体一些。
我们的目的主要是编译一个so动态链接库时,把自己object里面未定义的符号report出来就可以了,用–no-undefined和–unresolved-symbols=ignore-in-shared-libs应该可以的。

参考:
1.程序员的自我修养 ——链接、装载和库
2.深入理解计算机系统
3.C陷阱与缺陷 Andrew Koenig

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值