胶州住房和城乡建设厅网站,泉州免费建站模板,韩国设计app网站有哪些,旅游开发公司网站建设方案书文章目录 回顾一. 静态库1.代码传递的方式2.简易制作3.原理 二. 动态库1.简易制作2.基本原理 尾序 回顾 前面在gcc与g的使用中#xff0c;我们简单的介绍了动态库与静态库的各自的优点与区别#xff1a; 动态链接库#xff0c;也就是所有的程序公用一份代码,虽然方便省空间的使用中我们简单的介绍了动态库与静态库的各自的优点与区别 动态链接库也就是所有的程序公用一份代码,虽然方便省空间但是一旦链接库被删那么所有的程序将无法运行静态链接库就是所有程序都拷贝一份代码自己用这样虽然库删除之后会正常运行但是会使代码的空间异常的大通常在几十倍到几百倍左右。 详见——基本开发工具 那么今天就让我们通过动静态库的制作过程与基本原理进而更深一步了解动静态库吧
一. 静态库
1.代码传递的方式
我们要想让别人使用我们写的代码有两种方式 将源文件与头文件直接发给别人。将源文件打包成的库与头文件发给别人。 区别 第一种相当于把实现方法直接发给别人别人可以进行抄袭与学习以及使用几乎是把自己的劳动成果实现方法拱手让人。第二种相当于只把说明书头文件发送给了别人由于打包成了库因此实现方式别人看不到只能进行使用。 总结类比商品假如你买了个电脑第一种是只附带有说明书外加实现的具体方法。第二种是只附带了说明书。因此买的电脑如果是第一种电脑用坏了可以自己修甚至可以自己再造出一台电脑。如果是第二种用坏了自己还得去找人家花钱修。重点不管哪种方式头文件必不可少因为头文件是一份使用说明书一些具体的使用细节都在头文件中。而且在代码中包含头文件才能用里面的接口。如果不这样使用方式及其麻烦。 2.简易制作
源文件
#includemymath.h
int myerrno 0;
int add(int x,int y)
{return x y;
}
int sub(int x,int y)
{return x - y;
}
int product(int x,int y)
{return x * y;
}
int div(int x,int y)
{if(y 0){myerrno 1;return -1;}return x / y;
}头文件
//存放的是函数与变量的声明
extern int myerrno;
int add(int x,int y);
int sub(int x,int y);
int product(int x,int y);
int div(int x,int y);说明静态库的名称格式为——libXXX.a
生成静态库的指令
argc -rc [静态库的名称] [要生成静态库的目标文件]Makefile
#定义静态库变量的名称
liblibmymath.a
#目标文件生成静态库, $(lib)为变量
$(lib):mymath.oar -rc $ $^
#生成目标文件
mymath.o:mymath.cgcc -c $^
#清理文件
.PHONY:clean
clean:rm -rf *.a *.o mylib
#将生成的静态库进行打包
.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/mymathlibcp *.a mylib/mymathlibcp *.h mylib/includemake 生成 静态库与.o文件 2. make output将静态库与头文件进行拷贝打包 3. make clean 将多余的文件进行清理 此时我们的静态库就打包好了下面我们另起文件进行使用。 与mylib同目录下编写test.c
#includemymath.h
#includestdio.h
int main()
{printf(myerrno:%d 1 0 %d\n,myerrno,add(1,0));printf(myerrno:%d 1 - 0 %d\n,myerrno,sub(1,0));printf(myerrno:%d 1 * 0 %d\n,myerrno,product(1,0));printf(myerrno:%d 1 / 0 %d\n,myerrno,div(1,0));return 0;
}我们编译一下 可见我们所包含的头文件不在当前目录与默认路径usr/include因此找不到。 因此我们需要告诉编译器去哪找。
gcc test.c -I ./mylib/include因为头文件已包含文件名因此不用再说明。 可见我们函数定义还没有包含因此找不到定义 因此我们需要告诉编译器去哪找库。
gcc test.c -I ./mylib/include -L ./mylib/mymathlib/可见因为库名字未知且一个目录下可能有多个库因此我们还找不到定义。 因此我们需要告诉编译器库名字库真实的名字为去掉后缀.a 与前缀 lib。 gcc test.c -I ./mylib/include -L ./mylib/mymathlib/ -l mymath总结 -I(大写 i) 指定头文件的路径-L指定库所在路径-l(小写 L) 指定库的名称。且库的名称是去掉 lib 与 .a后缀。 3.原理 时间在预处理编译反汇编生成.o可重定向目标二进制文件之后。动作将静态库里面的内容拷贝, 与.o文件一起链接生成的.exe文件。 说明链接进行段表的合并符号表的重新定位其中段表的合并是把有效信息筛选无效信息删除符号表的重新定位指的时检查代码是否正确比如函数与某些全局变量的地址是否是有效的。 二. 动态库
1.简易制作
我们还是用之前的代码将myerrno删了)。
两个关键动作
生成.o文件并生成位置无关码 gcc -FPIC -c mymath.o生成动态库 gcc -shared -o libmymath.so mymath.oMakefile
liblibmymath.so$(lib):mymath.ogcc -shared -o $ $^mymath.o:mymath.cgcc -FPIC -c $^ .PHONY:cleanclean:rm -rf *.a *.so *.o .PHONY:outputoutput:mkdir -p lib/includemkdir -p lib/mymathlibcp *.so lib/mymathlibcp *.h lib/includemake生成动态库与.o文件 make output 动态库与.h文件进行打包 make clean 删除冗余的动态库文件与.o文件 同理我们使用一下库验证一下。
test.c
#includemymath.h
#includestdio.hint main()
{printf(1 1 %d\n,add(1,1));printf(1 - 1 %d\n,sub(1,1));printf(1 * 1 %d\n,product(1,1));printf(1 / 1 %d\n,div(1,1));return 0;
}同理我们直接使用之前静态库的结论进行编译链接。
gcc -o test test.c -I lib/include/ -L /lib/mymathlib/ -l mymath补充 ldd 【可执行文件】 #显示与可执行文件链接的可见在生成可执行程序是没问题的但是显示无法打开这个共享文件对象这是问什么呢 解释 在动态链接时我们是在可执行程序变成进程运行的同时链接到对应库当中其中库是文件需要打开才能被链接。因此需要让加载器去指定的路径下打开文件才能使用动态库。 注意前面的gcc 只是让编译器解决了如何找的问题如何让加载器打开还没有解决。其次静态链接因为是直接拷贝因此无需关心打开的问题。 因此我们需要将让编译器想办法在默认路径下打开库文件。 直接拷贝到默认路径最常用 可见是链接成功的可执行程序也能正常的执行不过因为要拷贝到系统的路径下所以我们需要sudo 进行提权。 在默认路径下建立对应静态库的软链接 与第一种方式同理唯一需要说明的是对不在同一目录下建立软链接需要使用绝对路径而不是相对路径。 修改环境变量LD_LIBRARY_PATH(可能会没有) 说明只需要后跟:与动态库所在的路径即可。至于名称我们在链接形成可执行程序时已经知道了。注意这里环境变量在重启时就没有了这是比较恶心的一点。 添加配置文件 su / su - 切换到root用户进入 /etc/ld.so.conf.d/添加一个.conf结尾的任意名称的文件vim 此文件切换到 Insert模式添加动态库的路径保存并退出。使用 ldconfig更新此配置文件。 图解验证 2.基本原理 先来铺垫一下我们编译器与链接器处理代码的过程
预处理完成头文件的替换条件编译中代码的裁剪宏的替换等。预编译完成对语义分析词法分析语法分析符号汇总等检查语法错误最终转换为汇编代码。汇编完成符号表与段表的生成并将代码转换为二进制代码。链接完成符号表的重定位与段表的合并并生成可执行程序。
那可执行程序里面存放的是什么呢
我们反汇编一下
objdump -S [可执行程序]可见是一些指令级别的东西这里我们或许还能勉强看懂一些汇编里面还存放着地址。因此我们可以从中得知可执行程序在还没有被加载时就已经存在地址了。 那么问题来了这里的地址是物理地址还是虚拟地址 肯定是虚拟地址是要给进程地址空间使用的物理地址是操作系统在程序加载之后申请的。 这是编译的结论接下来我们的程序是如何加载到内存当中的呢 我们先就可执行程序来进行讨论我们编译好的可执行程序是在磁盘当中的在加载时必然要被加载到内存当中。 对于操作系统来说可执行程序在加载时必然要变为进程之前我们已经了解过进程是 PCB数据结构 以及代码和数据。 其中PCB在Linux中为stuct tasks_struct 包含着 页表 进程地址空间struct mm_struct 管理文件的(struct files_struct)等对象。 那在程序加载时必然要先形成进程代码和数据可以后面用时再加载。那进程的地址空间首先要先加载才能保证后续的正常运行。
至于进程的地址空间的加载我们用图辅助理解 代码在进行加载时通过页表其指令在进程地址空间中是虚拟地址也就是编译生成的地址而实际执行指令的物理地址在加载时就通过页表进行填充。这样进程便可通过指令的虚拟地址通过页表获取到指令的物理地址进而执行指令。除此之外加载时要想找到可执行程序还得进程的exe即可执行程序的路径。这种信息在进程加载时即可进行获取。 代码现在成功加载到内存中了那指令是如何运行的呢
首先万事开头难如何读取到程序的第一行指令很关键因此会设置程序入口地址以便接下来的执行。
其次CPU首先通过指令寄存器拿到指令的虚拟地址然后通过页表进行映射成物理地址然后根据指令的具体信息进行执行然后接着执行下一句代码如此循环往复。 说明在加载中程序指令原本的虚拟地址在内存中变为了物理地址而原来的虚拟地址则给了进程地址空间这样才讲的通。 其次数据我们可以在需要时加载在加载时触发缺页中断让操作系统将页表进行填充即可。 代码与数据如何加载我们已经讲的差不多了那动态库是如何加载的呢 在这之前我们已经达成了共识动态库是共享库即多个进程都可以使用。 那么便可大致画出 可见动态库是在加载过程中与进程产生链接的。 那链接到进程地址空间的什么位置呢 进程地址空间的共享区这个共享区很大足够跟多个动态库进行链接。 既然在可能有多个共享库进行链接那么如何进行链接,才能保证能找到指定的共享库呢 我们可以采用起始地址 偏移量的方式从而使函数在在找库时只需知道偏移量即可。偏移量的设定与动态库生成中的位置无关码有关。 拓展在进行链接时动态库也可能会产生缺页中断的现象即用时再进行加载。 补充
第三方库即自己写的库在进行链接时必须要指定库名字如果一个库的方法实现有动态库也有静态库那么默认优先加载动态库。 总结
静态库的原理与简易制作。动态库的原理与简易制作。动态库加载的原理进程地址空间程序的加载。
尾序 如果有所帮助的话不妨点个赞鼓励一下吧