什么是库
库是写好的现有的,成熟的,可以复⽤的代码。比如,在平时编写代码时,许多函数都是通用的,如果没有库,人们在编写代码时就需要将这些通用的函数都自己实现一份。将这些通用的函数都编写到一起,这样,就形成了一个库。
在Linux下:
静态库:xxx.a
动态库:xxx.so
在windows下:
静态库:xxx.lib
动态库:xxx.dll
库的制作
动静态库中,要不要包含main函数??
- 如果自己写的库不想被别人链接,可以写一个main函数
静态库
所有的库(无论是动的还是静的),本质都是源文件对应的.o
静态库的本质就是.o文件打了一个包,或者说本质是一种归档文件,归档后只需要使用gcc/g++直接进行链接即可.
ar表示将文件进行归档,归档后的文件叫libmyc.a,即一个静态库,这个静态库的库名叫“myc”。-rc表示,如果将.o文件进行打包时,有新增的文件,则直接打包进去即可(c),如果.o文件的内容更新了,直接将更新后的.o文件对包中的对应源文件进行替换即可(r)。简单讲就是:需要替换的就替换,有需要一起打包的新源文件就直接一起打包
讲.o文件与库文件进行链接:
-L.表示在当前路径下查找库文件,-lmyc表示要链接的库叫"myc"
如果要链接任何非c/c++标准库,都需要指明-L -l,意思是要指明在哪里查找库,要链接的库叫什么
为了方便对库文件进行管理,可以将需要用到的库文件管理在“lib”目录下
在目标文件链接库文件时:
链接完后,就形成了可执行程序:
库也是被安装到系统中的。linux下,库在/usr/lib64路径下,头文件在/usr/include路径下
对于库的安装,就是将库的头文件与库文件拷贝到系统指定目录下,在链接时,只需要“-l+库名”就可以了。
动态库
制作动态库与静态库类似,也是将文件打包成库。
-fPIC::产⽣位置⽆关码(positionindependentcode)
-shared:表示形成的是动态库
形成静态库需要使用到ar指令,因为静态库是一个归档文件,而动态库不是
对库进行链接会发现,能生成指定的可执行程序,但是依然会报找不到库的错误
检查可执行程序链接的库,发现,真的是无法找到库
为什么会这样??
- 因为可执行程序所依赖的动态库是需要被系统知道的,在创建可执行程序时,只是告诉了gcc动态库的位置。系统≠gcc
静态库为什么没有这个问题?
- 因为静态库在链接时,是直接将库的实现拷贝到可执行程序里
怎么解决:
因为系统是去指定的路径下找库,所以第一种方法
- 将自己写的动态库拷贝到系统指定路径下即可
- 第二种方法
软链接
- 第三种方法
OS除了在指定路径下查找动态库,也会在该环境变量下查找:LDLIBRARYPATH
该环境变量通常是空的
将动态库的路径导入到这个环境变量中:
一旦关闭了shell,导入的这个环境变量就没了
- 第四种方法
更改系统的配置文件
这个路径下用于存放 动态链接器(ld.so) 的额外配置文件,这些文件告诉系统在哪些目录中搜索共享库(.so
文件)。
创建一个我们自己的conf配置文件,并将动态库路径写进去
使用这个指令重新加载配置文件
四种方法总结:
- 将动态库拷贝到系统指定路径下
- 使用软链接
- 将动态库路径写入LDLIBRARYPATH路径中去
- 向/etc/ld.so.conf/路径下添加配置文件
当动静态库同时存在时,可执行程序会链接哪个库呢?
- 动态库
链接任何库之前,库都必须存在。像上面动静态库都存在,想链接静态库就需要使用-static ,但前提条件是静态库必须存在
gcc/g++默认使用动态库
在Linux系统下,默认情况安装的大部分库,默认都是优先安装动态库
库:应用程序== 1:N
vs不仅仅形成可执行程序,也能形成静态库
目标文件
形成可执行程序的过程:预处理、编译、汇编、链接。但其实归根揭底就两步:编译、链接
形成的.o文件全称叫做:可重定向目标文件
进行链接其实就是将所有.o进行链接。
为什么要先编译成.o文件?
在一个大项目中,有许多的.c文件,如果不先编译成.o文件而是直接将.c文件经过编译器输出成可执行程序,那么当程序中某个.c文件出现问题时,就需要将出错的.c文件修改再将所有文件进行重新编译。
有了.o文件,就只需要将出错的.c文件重新编译就可以了
ELF⽂件
我们知道,可执行程序是有自己固定的格式的,会分为多个模块的,比如:数据段,代码段…
动静态库、可执行程序、.o文件也是如此,它们是以一定的格式将内容放入到二进制文件中的,也就是ELF格式
我们自己的可执行程序是有上图这般的格式的
最常见的部分:
代码节(.text):⽤于保存机器指令,是程序的主要执⾏部分。
数据节(.data):保存已初始化的全局变量和局部静态变量。
文件被编译后,代码则在代码节中,数据则在数据节中。数据节与代码节分别会占用数个section
size计算得出的是程序中几个数据节的大小
ELF从形成到加载轮廓
ELF形成可执⾏
step-1:将多份 C/C++ 源代码,翻译成为⽬标 .o文件+动静态库
step-2:将多份 .o ⽂件section进⾏合并
将多个.o文件、动静态库的对应的section部分的节进行合并,形成大的可执行
ELF可执⾏⽂件加载
⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进⾏Section合并,形成segment(段)。
合并原则:相同属性,⽐如:可读,可写,可执⾏,需要加载时申请空间等.(比如字符串常量区,代码区就会进行合并)
很显然,这个合并⼯作也已经在形成ELF的时候,合并⽅式已经确定了,具体合并原则被记录在了 ELF的 程序头表 (Program header table) 中
Section Headers是一个数组。可以把他理解为一个线性数组,当我们要拿其中的某一个section时,就是从这个线性的数组中取出数据。
ELF格式中,有一个data节与bss节。
data中保存的已初始化的全局变量和静态变量 的 值。
在bss中,保存的是未初始化的全局变量和静态变量,其核心特点是 在文件中不占用实际存储空间,仅在程序加载到内存时分配并初始化为零。
比如当我们有一个程序中有50个int类型的全局变量,但没有初始化,那么这50个全局变量不需要保存在data节中,只需要在bss节中用一个int[50]来记录(没有初始化的全局变量的初始值为0),当我们加载这个程序的时候,再将Int[50]展开,分配空间并初始化为0
查看ELF Header
eadelf -h +要查看的程序
每个ELF区域与文件的偏移量之间存在联系
理解连接与加载
静态库是如何形成可执行程序的:
静态链接
⽆论是⾃⼰的.o,还是静态库中的.o,本质都是把.o⽂件进⾏连接的过程
所以:研究静态链接,本质就是研究.o是如何链接的
对.o文件进行反汇编,我们看到callq的地址是00 00 00 00,这是因为编译器编译.c文件时不知道调用函数的地址是多少,所以先暂时设为0
尽管调用了一个没有声明,没有函数体的函数的函数,但是编译依然没有报错。这就证明了编译器在编译.c文件时,是不认识调用的函数的,不知道函数的真实地址是多少。
那这个地址会在什么时候进行填充修正呢?
- 链接的时候
printf底层实际上调用的就是puts函数
观察hello.o的符号表
当将这些.o文件进行链接时,实际上就是将文件中的这些未定义的模块去其他模块中找
当链接后,callq 00 00 00 00的地址,全都填充为具体的值了
最终: 两个 .o 的代码段合并到了⼀起,并进⾏了统⼀的编址 。链接的时候,会修改 .o 中没有确定的函数地址,在合并完成之后,进⾏相关 call 地址,完成代码调 ⽤
静态链接就是把库中的.o进⾏合并,和上述过程⼀样
所以链接其实就是将编译之后的所有⽬标⽂件连同⽤到的⼀些静态库运⾏时库组合,拼装成⼀个独⽴ 的可执⾏⽂件。其中就包括我们之前提到的地址修正,当所有模块组合在⼀起之后,链接器会根据我 们的.o⽂件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从⽽修正它们的地址。这 其实就是静态链接的过程。
所以,链接过程中会涉及到对.o中外部符号进⾏地址重定位
ELF程序如何加载到内存的,ELF如何转换成进程的(逻辑地址,物理地址),虚拟地址空间:
一个可执行程序,如果没有被加载到内存中,该可执行程序有没有地址?
- 有地址
当要查找某个模块时,只需要根据起始地址和偏移量就能查找到。
磁盘上的地址叫做“逻辑地址”。
后面优化成:每个程序只要一个segment,且起始地址为0,每个模块的偏移量都是根据这个一个segment来说的
当代计算机编址:平坦模式编址
这些地址也叫做线性地址
这前讲到线性地址的地方是关于程序的虚拟地址空间部分。
这里关于对磁盘上的可执行程序进行编址,其实就是虚拟地址的统一编址
当文件被编译好之后,函数之间互相调用的地址就是虚拟地址了