一、动态库和静态库
具体的动态库和静态库的相关内容👉点击跳转
Linux的库一般分为动态库和静态库:
静态库(.a):库文件以.a为后缀,程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):库文件以.so为后缀,程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
链接的本质:无非就是我们调用库函数的时候和标准库是如何关联的问题
库的名称:去掉前缀lib去掉后缀.so/.a剩下的就是库名称,比如libc.so就是C库
gcc 在编译时默认使用动态链接,而生成静态链接,我们需要在末尾带上-static:
安装静态库:
sudo yum install -y glibc-static
sudo yum install -y libstdc++-static
二、理解库
如果不想给对方我们的源代码,我们可以选择给用户提供我们的.o可重定位目标二进制文件(gcc -c 文件)与.h头文件。让用户用我们提供的.o文件进行链接即可。在编译时,只要把源文件编译成.o文件在将其链接便可形成一个可执行的程序:
通过gcc -o生成,不出意外,编译运行成功:
难道就这么简单吗?我们可以给对方提供.o(方法的实现),同时还有提供.h(里面包含着都有哪些方法),此时对方是能用的。但是如果存在很多.c文件呢?难道我们要把几千个.c文件全部编译成.o在加上头文件全部一个一个提供吗?那样太过于麻烦,为了让用户更好的使用库,我们就有把所有的.o文件打成一个包,给对方提供一个库文件即可!把多个.o合并成一个文件,这个文件就是库,把包方式的不同就分为了动态库和静态库*。
库的本质就是.o文件的集合。
准备的文件:
三、制作静态库
我们如果想自己写一个库,要不要在这个库里面写main函数呢?答案肯定是不要的,库是被别人用的,自己写的main会和库里的main发生冲突。我们可以站在编写库的角度和使用者的角度来制作:
编写库:创建Makefile:
将文件编译成.o文件
ar命令:把所有的.o打包起来,ar是归档。
-rc代表replace和create;比如:
ar -rc libmymath.a my_add.o my_sub.o
output:而我们要交付库,实际上要把库文件a.so以及匹配的头文件都交付给用户,而output就是一个发布的过程
使用
此时,我们用户如果需要只要把mylib.tgz拷贝过去:比如:cp mylib.tgz …/test,拷贝过去之后进行解压: tar xzf mylib.tgz
而所谓的安装其实就是在拷贝。直接把安装好的库使用起来:
头文件找不到?
编译器搜索头文件时默认在当前目录下搜索,在系统默认指定路径下搜索。虽然此时的mylib在当前路径下,但是头文件太深了,编译器找不到头文件,所以我们需要给gcc指定路径。带上-I ,指明在当前目录下的mylib目录下查找:
问题又来了,找不到库函数的实现。我们在形成可执行程序的时候,库文件要使用的话也要知道库所在的路径在哪里,系统的默认路径是/lib64。所以我们要带上-L。告诉库的路径在哪里。
但是如果要链接第三方的库,必须去指明库的名称(注意去掉前缀和后缀!)!!!也就是说,一定要告知路径下哪一个库,即使只有一个库,也要明确告知gcc要链接哪一个库(虽然我们以前写代码的时候,从来没有指明过库名称,这是因为gcc/g++默认帮我们填了,可以识别C/C++自带的库。自己写的要指明):
最终终于顺利完成!
上面说了那么多,总结一下:
-I:指明头文件的搜索路径
-L:指明库文件的搜索路径
-l:指明要链接哪个库,带上库的名称(去掉前缀和后缀)
gcc默认是动态链接的(建议行为)对于特定的一个库,究竟是动静态库,取决你提供的是动态库还是静态库。
库的安装(把库安装到系统头文件路径下):
把头文件和库文件拷贝进系统的路径下,gcc对于头文件的默认路径是:/usr.include;对于库文件的默认路径是:/lib64:
但是不太推荐这样使用:第三方库并没有经过测试,自己写的会污染库里面的其他文件。
四、制作动态库
首先我们需要把库文件全部编译成.o文件,这里与静态库不同,需要带上选项 -fPIC,形成与位置无关码:
gcc -c -fPIC my_add.c
什么是与位置的无关码的目标二进制文件:
静态库采用的是绝对编址
动态库采用的是相对编址,动态库中的指定函数的地址通过相对编址(库中的偏移地址+段起始地址):
动态库打包:-shared
gcc -shared -o libmymath.so my_add.o my_sub.o
使用动态库
但是这样子就可以吗?我们直接运行mymath:
运行不了,这是为什么?找不到库
我们此时已经告诉了库文件,路径和库名称,选项已经给gcc带上了。但是我们当编译完之后,和gcc还有关系吗?答案是无关的,接下来运行是和OS有关的,动态库是运行时才加载的,所以程序运行起来,OS和shell也是需要知道库是在哪里的!而我们自己制作的库并没有在系统路径下,OS无法找到!如何找到动态库:
把库路径添加到环境变量LD_LIBRARY_PATH,比如当前自己制作库的路径是 /home/hwc/dir/test/mylib/lib
直接运行:
但是我们自己定义的环境变量只是本次登录有效,如果想永久有效只能修改环境变量的配置,但是比较麻烦。想永久有效,除了把库拷贝到系统目录下之外,我们还有其他方法:
1.配置文件(/etc/ld.so.conf.d/):动态库进行对应搜索时可以采用自己定义conf文件找到动态库
2.建立软链接直接找到对应的库
把对应的动态库建立在系统的目录下:
总结一下:
运行动态库
1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改 LD_LIBRARY_PATH
3、ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新
4.创建软链接
五、动静态库的加载
静态库不需要加载,静态库把代码拷贝到可执行程序里,直接决定了当加载的时候在内存里代码和数据可能存在多份,会比较浪费空间,把静态库中拷贝到程序中的代码区里:
动态库加上fPIC形成位置无关码,采用相对编址方案,在程序链接时对应库当中的偏移量添加到可执行程序,运行时一旦库加载进来,经过地址空间映射,把库映射到地址空间之后,库也就具备了起始地址,通过偏移地址和起始地址这样就可以找到访问的函数:
系统层面上会维护动态库的起始地址,直接建立页表与内存的映射,也就可以跳转访问了,所以动态库加载一次就可以被多个进程共同使用了。而静态库可能有多个程序用了C库,加载到内存时,内存里可能会存在100份重复的代码。而动态链接不会出现重复的代码,减少内存。