------静态库(.a): 程序在编译链接的时候把库的代码链接(拷贝)到可执行程序中。程序运行时将不再需要静态库。
**------动态库(.so):**程序在运行时才去链接动态库的代码,在编译时把程序运行需要的函数的地址拷贝到可执行文件中。1.模拟实现库---准备工作
#创建四个文件模拟库(库)--实现加法和减法:
#四个文件的内容:
cs#pragma once #include<stdio.h> extern Add(int a,int b); #include"my_add.h" int Add(int a,int b) { printf("enter Add func,%d + %d = ?\n",a,b); return a + b; } #pragma once #include<stdio.h> extern int Sub(int a,int b); #include"my_sub.h" int Sub(int a,int b) { printf("enter Sub func,%d - %d = ?\n",a,b); return a - b; }
#创建main.c函数调用++Add++ 和++Sub++方法:
cs#include"my_add.h" #include"my_sub.h" int main() { int a=10; int b=20; int res = Sub(a,b); printf("result:%d\n",res); res = Add(a,b); printf("result:%d\n",res); return 0; }
#输出结果:
2.库文件的组成
#可重定位目标可执行文件,gcc -c将源文件编译变成二进制文件,此时还没有进行链接
#将加减形成的两个.o二进制文件和main.c放入一个文件夹中进行链接:
#根据编译报错发现头文件必不可少,将头文件也移至test目录下,代码成功运行:
如果我们不想给对方我们的源代码,可以只给对方提供.o可重定位目标二进制文件和其所需的头文件,再进行链接。未来我可以给对方提供.o(方法的实现),.h(都有什么方法)。我们可以尝试将所有".o文件"打一个包提供一个.o文件,给对方提供作为库文件使用,采用不同的打包方式/打包工具,产生动态库和静态库。
3.模拟实现一个静态库(创建和使用)
3.1创建者视角创建一个静态库
将所有的.o文件打包成一个.o文件(库文件),利用Makefile实现。
此时形成的libmymath.a是不能作为一个库直接给别人用的,交付库本质是将库文件(.a/.so)+匹配的头文件同时交给别人。我们要将所有的.o文件和对应的.h文件都进行打包。
make 和make output后形成mylib,tar压缩成压缩包mylib.tgz上传供他人使用:
3.2作为使用者使用我们之前所打包压缩的第三方库:
我们以前写代码时在链接时为什么gcc/g++默认能够识别C/C++自带的标准库;如果是第三方库我们要使用、链接第三方库,必须指明头文件的路径、指明库所在的路径、指明库名称(注意此处的库名称需去掉前缀lib和后缀.a !)。
gcc默认是动态链接的(建议选项)。实际上gcc在链接时可能会链接很多个库,其中存在静态库也存在动态库;而gcc对于特定库链接方式取决于库本身是动态还是静态。
ldd命令列出程序或库文件的动态依赖关系(所依赖的共享库列表)。根据下图信息可以发现我们在链接libmylib.a静态库时显示采用的是动态链接(因为存在链接系统的C语言动态库),当该可执行程序链接的全是静态库时才会显示静态链接。
每次链接时需要指明头文件所在路径及库文件所在路径还有库名称太过繁琐;直接将我们的第三方库安装到系统库文件下和系统的头文件下:
4.模拟实现一个动态库
4.1创建者视角创建一个静态库
将多个源文件编译形成多个.o文件并打包成动态库文件libmymath.so
将形成的库文件和其所需的头文件打包形成目录文件mylib
将mylib与main.c文件放入test目录下供测试
4.2使用动态库在运行时需要库路径来加载库
gcc在链接时已经指明了库文件、路径和库名称;在gcc成功链接后要运行链接形成的可执行程序mymath时报错,此时程序被加载到内存形成进程,程序运行起来也需要加载动态库,此时就是因为程序运行起来也需要加载库,但是操作系统没有库的路径找不到所以报错。
---在环境变量 $LD_LIBRARY_PATH下添加第三方动态库路径(只在本次登录有效)
---修改配置文件,将库文件路径添加到配置文件/etc/ld.so.conf.d/下(操作较麻烦)
---建立软链接,在当前目录下建立库文件的软链接文件(但只有该目录下可用)
---建立软链接,在系统 /lib64/下建立该库文件的软链接文件
5.安装并使用第三方库
5.1安装一个第三方库(例:libncurses5-dev简易的图形化窗口)
5.2建立一个文件测试库
cs#include <ncurses.h> int main() { initscr(); // 初始化屏幕 printw("Hello World"); // 在屏幕上打印文本 refresh(); // 刷新屏幕以显示文本 getch(); // 等待用户输入 endwin(); // 结束ncurses模式 return 0; }
5.3链接时指明第三方库名(不指明报链接错)
6.动静态库的加载理解
6.1静态库需要加载吗?
------不需要。静态库在链接时已经将所需库中方法拷贝进.O文件中。虚拟地址空间是在编译时就形成的,按照全局数据区、代码区...来分配代码和数据。拷贝的时候就是将库中的代码展开拷贝至可执行程序的代码区(库中方法只能映射在代码区!)。未来这部分代码,必须通过相对确定的地址位置来进行访问(绝对编址:在我们虚拟地址空间中全0-全F的某一个字段)。
6.2那么动态库是如何加载的呢?
将动态库中指定函数的地址(在动态库中的地址),写入到我们的可执行程序中。绝对编址和相对编址(与位置无关)。动态库当中特定函数的地址在编址时采用的是偏移地址(start+偏移量)从库的起始位置开始,printf/cout函数...,将我们所需函数在库中的偏移量地址填入到可执行程序里。当程序运行访问printf知道它在外库中,操作系统就去将库加载进来,把库中的内容通过页表映射到堆栈之间的共享区,这个库的起始地址就确定了,再根据偏移量+共享库的起始地址就能找到所需函数。
-fPIC形成与位置无关码,即相对编制
-shared 打包,形成动态库特定格式
在程序运行需要动态库时,需知道库的地址,因为动态链接的可执行文件只有函数相对位置