库文件的概念
在很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源代码文件只编译一次,然后在需要的时候将它们链接进不同的可执行文件中。虽然这项技术能够节省编译时间,但其缺点是在链接的时候仍然需要为所有目标文件命名。此外,大量的目标文件会散落在系统上的各个目录中,从而造成目录中内容的混乱。
为解决这个问题,可以将一组目标文件组织成一个被称为对象库的单元。对象库分为两种:静态的和共享的。共享库是一种更加现代化的对象库,它比静态库更具优势。
静态库也被称为归档文件,它是Unix/linux系统提供的一种库。
(1)静态库的特征
1)静态库在使用时,直接把代码复制到目标文件中。
2)优点:不需要跳转,效率比较好,脱离静态库文件。
3)缺点:目标文件会比较大,修改和维护都不太方便。
(2)共享库的特征
1)共享库在使用时,直接把点吗所对应的地址复制过来。
2)优点:目标文件比较小,修改和维护都方便。
3)缺点:需要跳转,效率比较低,不能脱离共享库文件。
(3)基本命令
ldda.out => 表示查看a.out所链接的共享库信息
gcc/cc-static xxx.c => 表示以静态库的方式进行处理
比较发现,静态库方式生成的文件比较大。
静态库的生成和使用步骤
(1)静态库的生成步骤
1)编写源代码(xxx.c文件)
vi add.c文件
2)只编译不链接生成目标文件(xxx.o完呢减)
gcc/cc -c add.c
3)生成静态库文件
ar -r/*插入*/ lib库名.a 目标文件
ar-r libadd.a add.o
注意:
静态库文件名的名字规则:以lib开头,以.a结尾
静态库文件名和库名是不同的概念,库名没有前缀和后缀
(2)静态库的使用步骤
1)编写测试源代码(xxx.c)
vi main.c文件
2)只编译不链接生成目标文件(xxx.o)
gcc -c main.c
3)链接测试文件和静态库文件,链接的方式有三种:
a.直接链接
gcc main.o libadd.a
b.使用编译选项进行链接
gcc main.o -l 库名 -L 库文件所在的路径
gcc main.o -l add -L .
c.配置环境变量LIBRARY_PATH
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.o -l add
共享库的生成和使用步骤
(1)共享库的生成步骤
1)编写源代码(xxx.c)
vi add.c文件
2)只编译不链接生成目标文件(xxx.o)
gcc -c -fpic/*小模式,代码少*/ add.c
3)生成共享库文件
gcc -shared 目标文件(xxx.o) -o lib库名.so
gcc -shared/*共享*/ add.o -o libadd.so
(2)共享库的使用步骤
1)编写测试源代码(xxx.c)
vi main.c文件
2)只编译不链接生成目标文件(xxx.o)
gcc/cc -c main.c
3)链接测试文件和共享库文件,链接的方式有三种:
a.直接链接
gcc main.o libadd.so
b.使用编译选项进行链接
gcc main.o -l 库名 -L 库文件所在的路径
gcc main.o -l add -L .
c.配置环境变量LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
gcc main.o -l add
注意:
共享库的使用要求配置环境变量LD_LIBRARY_PATH的值,主要解决运行时找不到共享库的问题。
共享库的动态加载
(1)dlopen函数
#include
void *dlopen(const char *filename, int flag);
第一个参数:字符形式的共享库函数名
第二个参数:标志
RTLD_LAZY- 延迟加载
RTLD_NOW - 立即加载
返 回 值:通用类指针,成功返回句柄,暂时理解为首地址,失败返回NULL
函数功能 :
主要用于打开和加载动态库
(2)dlerror函数
char *dlerror(void);
函数功能:
主要用于获取dlopen等函数调用过程发生的最近一个错误的详细信息
(3)dlsym函数
void* dlsym(void *handle, const char *symbol);
第一个参数:句柄,也就是dlopen函数的返回值
第二个参数:字符串形式的符号,表示函数名
返回值:成功返回函数在内存中的地址,失败返回NULL
函数功能:
主要用于根据句柄和函数名获取在内存中对应的地址
(4)dlclose函数
int dlclose(void *handle);
函数功能:
主要用于关闭参数handle指定的共享库,成功返回0,失败返回非0,当共享库不再被任何程序使用时,则回收共享库所占用的内存空间。
注意:
编译链接时需要增加选项: -ldl
#include
#include
#include
int main(void)
{
//打开一个动态库
void *handle = dlopen("./dynamic_add/libadd.so", RTLD_LAZY);
//判读打开是否成功,返回值为NULL表示打开失败
if (NULL == handle)
{
//打印失败原因
printf("%s\n", dlerror());
exit(-1);
}
printf("打开共享库成功!\n");
//读取动态库中的函数地址(函数指针的类型必须和需要读取的函数一致)
int (*p_func)(int, int) = dlsym(handle, "add");
//判断读取是否成功,返回值为NULL表示读取失败
if (NULL == p_func)
{
printf("%s\n", dlerror());
exit(-1);
}
//调用动态库的函数
printf("%d\n", p_func(2, 8));
//最后关闭动态库,返回值非0表示关闭失败
if (0 != dlclose(handle))
{
printf("%s\n", dlerror());
exit(-1);
}
printf("关闭共享库成功!\n");
return 0;
}
总结:
由于与静态库相比,共享库存在很多优势,因此再当代Unix/linux系统上共享库用得很多。共享库的又是主要源自这么一个事实,即当一个程序与库链接时,程序所需的目标模块的副本不会被包含进结果可执行文件中。相反,(静态)链接器将会在可执行文件中添加与程序在运行时所需的共享库相关的信息。当文件执行时,动态链接器会使用这些信息来加载所需的共享库。在运行时,所有使用同一共享库的程序共享该库在内存中的副本。由于共享库不会被复制到可执行文件中,并且在运行时所有程序都使用共享库在内存中的单个副本,因此共享库能够降低系统所需的磁盘空间和内存。