共计 5507 个字符,预计需要花费 14 分钟才能阅读完成。
linux 下有两种库: 动静库和动态库(共享库)
二者的不同点在于代码被载入的时刻不同。
动态库的代码在编译过程中曾经被载入可执行程序, 因而体积比拟大。
动静库 (共享库) 的代码在可执行程序运行时才载入内存,在编译过程中仅简略的援用,因而代码体积比拟小。
不同的应用程序如果调用雷同的库, 那么在内存中只须要有一份该动静库 (共享库) 的实例。
动态库和动静库的最大区别, 动态状况下, 把库间接加载到程序中, 而动静库链接的时候, 它只是保留接口, 将动静库与程序代码独立, 这样就能够进步代码的可复用度,和升高程序的耦合度。
动态库在程序编译时会被连贯到指标代码中,程序运行时将不再须要该动态库。
动静库在程序编译时并不会被连贯到指标代码中,而是在 程序运行是才被载入,因而在程序运行时还须要动静库存在
一 动态库
这类库的名字个别是 libxxx.a;利用动态函数库编译成的文件比拟大,因为整个 函数库的所有数据都会被整合进指标代码中,他的长处就不言而喻了,即编译后的执行程序不须要内部的函数库反对,因为所有应用的函数都曾经被编译进去了。当然这也会成为他的毛病,因为如果动态函数库扭转了,那么你的程序必须从新编译。
动态库的代码在编译时链接到应用程序中,因而编译时库文件必须存在, 并且须要通过“-L”参数传递门路给编译器, 应用程序在开始执行时,库函数代码将随程序一起调入过程内存段直到过程完结,其执行过程不须要原动态库存在。
在 UNIX 中, 应用 ar 命令创立或者操作动态库
ar archivefile objfile
archivefile:archivefile 是动态库的名称
objfile:objfile 是已.o 为扩展名的两头指标文件名,能够多个并列
参数 意义
-r 将 objfile 文件插入动态库尾或者替换动态库中同名文件
-x 从动态库文件中抽取文件 objfile
-t 打印动态库的成员文件列表
-d 从动态库中删除文件 objfile
-s 重置动态库文件索引
-v 创立文件冗余信息
-c 创立动态库文件
example:
/****************** hello.h **************/
void hello(void);
/****************** hello.cpp **************/
#include<iostream>
#include"hello.h"
using namespace std;
void hello(void)
{cout <<"Hello"<<endl;}
/****************** main.cpp **************/
#include"hello.h"
int main(int argc,char *argv[])
{hello();
return 0;
}
须要 C /C++ Linux 服务器架构师学习材料加群 563998835(材料包含 C /C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),收费分享
1. 编译成动态库
无论动态库,还是动静库,都是由.o 文件创建的。因而,咱们必须将源程序 hello.c 通过 gcc 先编译成.o 文件。
hc@linux-v07j:~/weiming/tt> g++ -o hello.o -c hello.cpp
hc@linux-v07j:~/weiming/tt> ar cqs libHello.a hello.o
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp
2. 链接
hc@linux-v07j:~/weiming/tt> g++ main.cpp libHello.a -o Out1(g++ -o aOut main.cpp ./libHello.a 或者 g++ -o aOut main.cpp -L./ -lHello)
留神:如果 hello() 外面还应用了其余的库函数比方 pthread_create,则最初生成 Out1 时还需 -lpthread,但 ar 时能够不必,只须要在 include 的头文件中找到函数符号申明即可,但最终生成可执行文件时须要找到所有的符号定义。
hc@linux-v07j:~/weiming/tt> ls
hello.cpp hello.h hello.o libHello.a main.cpp Out1
hc@linux-v07j:~/weiming/tt> ldd Out1
linux-gate.so.1 => (0xffffe000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e36000)libm.so.6 => /lib/libm.so.6 (0xb7e11000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7e06000) libc.so.6 => /lib/libc.so.6 (0xb7ce3000) /lib/ld-linux.so.2 (0xb7f1b000)
二:动静库
这类库的名字个别是 libxxx.so; 绝对于动态函数库,动静函数库在编译的时候 并没有被编译进指标代码中,你的程序执行到相干函数时才调用该函数库里的相应函数,因而动静函数库所产生的可执行文件比拟小。因为函数库没有被整合进你的程序,而是程序运行时动静的申请并调用,所以程序的运行环境中必须提供相应的库。动静函数库的扭转并不影响你的程序,所以动静函数库的降级比拟不便
不同的 UNIX 零碎, 链接动静库办法,实现细节不一样
编译 PIC 型.o 两头文件的办法个别是采纳 C 语言编译器的 -KPIC 或者 -fpic 选项, 有的 UNIX 版本 C 语言编译器默认带上了 PIC 规范. 创立最终动静库的办法个别采纳 C 语言编译器的 - G 或者 -shared 选项,或者间接应用工具 ld 创立。
** 最次要的是 GCC 命令行的一个选项:
-shared 该选项指定生成动静连贯库(让连接器生成 T 类型的导出符号表,有时候也生成弱连贯 W 类型的导出符号),不必该标记内部程序无奈连贯。相当于一个可执行文件 -fPIC:示意编译为地位独立的代码,不必此选项的话编译后的代码是地位相干的所以动静载入时是通过代码拷贝的形式来满足不同过程的须要,而不能达到真正代码段共享的目标。(转者注:共享库各段的加载地址并没有定死,能够加载到任意地位,因为指令中没有应用相对地址(绝对于链接后的可执行文件各 segment 来说),因而称为地位无关代码)-L.:示意要连贯的库在当前目录中 -ltest:编译器查找动静连贯库时有隐含的命名规定,即在给出的名字后面加上 lib,前面加上.so 来确定库的名称 **
这里别离将源文件 d1.c 和 d2.c 编译为动静库 d1.so 和 d2.so.
/************ d1.h***************/
void print();
/*************** d1.cpp *******************/
#include <iostream>
#include "d1.h"
using namespace std
int p = 1;
void print()
{cout<< p <<endl;}
/************ d2.h***************/
void print();
/*************** d2.cpp *******************/
#include <iostream>
#include "d2.h"
using namespace std;
int p = 2;
void print()
{cout<< p <<endl;}
LINUX 和其余 gcc 编译器
g++ -fpic -c d1.cpp d2.cpp / 编译为.o 为扩展名的两头指标文件 d1.o,d2.o/
g++ -shared -o libd1.so d1.o /依据两头指标文件 d1.o 创立动静库文件 d1.so/
g++ -shared -o libd2.so d2.o /依据两头指标文件 d2.o 创立动静库文件 d2.so/
或者间接一步到位
g++ -O -fpic -shared -o libd1.so d1.cpp
g++ -O -fpic -shared -o libd2.so d2.cpp
某些版本的 gcc 上也能够应用 - G 替换 -shared 选项
调用动静库
隐式调用动静库
/************** main.cpp *********************/
void print(); // 或者用 #include"d1.h"(#include"d2.h")替换
int main(int argc,char *argv[])
{print();
}
cp ./libd1.so libd.so (cp ./libd2.so libd.so)
g++ -o dOut main.cpp ./libd.so (或者 g ++ -o dOut main.cpp -L./ -ld)
hc@linux-v07j:~/weiming/tt/dd> ldd dOut
linux-gate.so.1 => (0xffffe000) libd.so => ./libd.so (0xb7f0f000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e2b000)
libm.so.6 => /lib/libm.so.6 (0xb7e06000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7dfa000) libc.so.6 => /lib/libc.so.6 (0xb7cd8000) /lib/ld-linux.so.2 (0xb7f12000)
ldd 模仿运行一遍 main,在运行过程中做动静链接,从而得悉这个可执行文件依赖于哪些共享库,每个共享库都在什么门路下,加载到过程地址空间的什么地址。
总之,共享库的搜寻门路由动静链接器决定,从 ld.so(8)的 Man Page 能够查到共享库门路的搜寻程序:
- 首先在环境变量 LD_LIBRARY_PATH 所记录的门路中查找。
- 在程序链接时指定的 rpath 中查找,能够 readelf binfile | grep RPATH。
- 而后从缓存文件 /etc/ld.so.cache 中查找。这个缓存文件由 /sbin/ldconfig 命令读取配置文件 /etc/ld.so.conf 之后生成。
(也能够在 ld.so.conf.d 目录下减少 .conf 文件,外面写入库门路,在 ld.so.conf 中 include ld.so.conf.d/.conf)
- 如果上述步骤都找不到,则到默认的零碎门路中查找,先是 /usr/lib 而后是 /lib。
不同的 UNIX 所依赖的动静库查找门路环境变量名称各不相同
UNIX 版本 动静库查找门路环境变量
AIX LIB_PATH
LINUX LD_LIBRARY_PATH
HP_UNIX PAHT
SCO UNIX LD_LIBRARY_PATH
动态链接库取代动态库的益处之一就是能够随时升级库的内容。
当动静库被接口完全相同的库文件取代后, 可执行程序能迅速的切换到新动静库中代码,省去了编译的麻烦。
显式调用动静库
显式调用动静库, 编译时无需库文件, 执行时动静可存储于任意地位, 库里共享对象必须先申请后应用, 不同动静库版本, 只有其共享对象接口雷同, 就能够间接动静加载。留神增加 “-ldl” 编译参数。
// 关上动静库
#include<dlfcn.h>
void *dlopen(const char * pathname,int mode);
// 获取动静库对象地址
include<dlfcn.h>
void *dlsym(void *handle,const char *name);
// 谬误检测
include<dlfcn.h>
char *dlerror(vid);
// 敞开动静库
include<dlfcn.h>
int dlclose(void * handle);
动静库的加载或多或少会占用肯定的系统资源, 比方内存等。因而当不须要或者一段时间内不须要共享动静库时就要卸载之。函数 dlclose 敞开参数 handle 所指向的动静库,卸载其所占的内存等资源, 此调用后参数 handle 有效。
实际上,因为动静库可能同时被多个过程共享, 当一个过程指向 dlclose 时,资源并不马上被卸载, 只有当全副过程都发表敞开动静库后, 操作系统才开始回收动静库资源。
总结:
编译动态库时先应用 - c 选项, 再利用 ar 工具产生. 编译动静库的形式依不同版本的 UNXI 而定。隐式调用动静库与动态库的用法相一致, 而显示调用动静库则须要借助动静加载共享库函数族。
隐式调用动静库和动态库应用办法统一, 应用动态库和应用动静库编译成目标程序应用的 gcc 命令齐全一样,那当动态库和动静库同名时,gcc 命令会应用哪个库文件呢?
通过测试能够发现, 当动态库和动静库同名时,gcc 命令将优先应用动静库. 为了确保应用的是动态库, 编译时能够加上 -static 选项,因而多第三方程序为了确保在没有相应动静库时运行失常,喜爱在编译最初应用程序时退出 -static