静态库与动态库
一、库
1.合久必分——增量编译——易于维护,分久必合——库——易于使用。
2.链接静态库是将库中的被调用代码复制到调用模块中, 而链接共享库则只是在调用模块中,嵌入被调用代码在库中的(相对)地址。
3.静态库占用空间非常大,不易修改但执行效率高。共享库占用空间小,易于修改但执行效率略低。
4.静态库的缺省扩展名是.a,共享库的缺省扩展名是.so。
5.环境变量
C_INCLUDE_PATH - C头文件的附加搜索路径,相当于gcc的-I选项
CPATH - 同C_INCLUDE_PATH
CPLUS_INCLUDE_PATH - C++头文件的附加搜索路径
LIBRARY_PATH - 链接时查找静态库/共享库的路径
LD_LIBRARY_PATH - 运行时查找共享库的路径
•通过gcc的-I选项指定C/C++头文件的附加搜索路径:
# gcc calc.c cpath.c -I.
•将当前目录作为C头文件附加搜索路径,添加到CPATH环境变量中:
# export CPATH=$CPATH:. // export保证当前shell的,子进程继承此环境变量
# echo $CPATH
# env | grep CPATH
•也可以在 ~/.bashrc 或者 ~/.bash_profile,配置文件中写环境变量,持久有效:
export CPATH=$CPATH:.
执行
# source ~/.bashrc 或 # source ~/.bash_profile
//生效,以后每次登录自动生效。
头文件的三种定位方式:
1) #include "目录/xxx.h" - 头文件路径发生变化,需要修改源程序
2) C_INCLUDE_PATH/CPATH=目录 - 同时构建多个工程,可能引发冲突
3) gcc -I目录 - 既不用改程序,也不会有冲突
二、静态库
1. 创建静态库
-
编辑源程序:.c/.h
-
编译成目标文件:gcc -c xxx.c -> xxx.o
-
打包成静态库文件:ar -r libxxx.a xxx.o …
1.# gcc -c calc.c 2.# gcc -c show.c 3.# ar -r libmath.a calc.o show.o
ar指令:ar [选项] 静态库文件名 目标文件列表
-r - 将目标文件插入到静态库中,已存在则更新
-q - 将目标文件追加到静态库尾
-d - 从静态库中删除目标文件
-t - 列表显示静态库中的目标文件
-x - 将静态库展开为目标文件
注意:提供静态库的同时也需要提供头文件
2. 调用静态库
-
直接调用
# gcc main.c libmath.a
-
或通过LIBRARY_PATH环境变量指定库路径
# export LIBRARY_PATH=$LIBRARY_PATH:. # gcc main.c -lmath (环境法)
-
通过gcc的-L选项指定库路径
# unset LIBRARY_PATH # gcc main.c -lmath -L. (参数法)
一般化的方法:gcc .c/.o -l<库名> -L<库路径>
3. 运行
在可执行程序的链接阶段,已将所调用的函数的二进制代码,复制到可执行程序中,因此运行时不需要依赖静态库。
范例:show.h、show.c、calc.h、calc.c、math.h、main.c
show.h
#ifndef _SHOW_H
#define _SHOW_H
void show (int a, char op, int b, int c);
#endif//_SHOW_H
show.c
#include <stdio.h>
#include "show.h"
void show (int a, char op, int b, int c)
{
printf ("%d %c %d = %d\n", a, op, b, c);
}
calc.h
#ifndef _CALC_H
#define _CALC_H
int add (int a, int b);
int sub (int a, int b);
#endif//_CALC_H
calc.c
#include "calc.h"
int add (int a, int b)
{
return a + b;
}
int sub (int a, int b)
{
return a - b;
}
math.h
#ifndef _MATH_H
#define _MATH_H
#include "calc.h"
#include "show.h"
#endif//_MATH_H
main.c
#include "math.h"
int main (void)
{
show (30, '+', 20, add (30, 20));
show (30, '-', 20, sub (30, 20));
}
4. 静态链接库的优缺点
优点:
(1) 代码装载速度快,执行速度略比动态链接库快。
(2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
缺点:
使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。
三、共享库
1. 创建共享库
-
编辑源程序:.c/.h
-
编译成目标文件:gcc -c -fpic xxx.c -> xxx.o
-
链接成共享库文件:gcc -shared xxx.o … -o libxxx.so
# gcc -c -fpic calc.c # gcc -c -fpic show.c # gcc -shared calc.o show.o -o libmath.so //或一次完成编译和链接 # gcc -shared -fpic calc.c show.c -o libmath.so
PIC (Position Independent Code):位置无关代码。可执行程序加载它们时,可将其映射到其地址空间的任何位置。
-fPIC : 大模式,生成代码比较大,运行速度比较慢,所有平台都支持。
-fpic : 小模式,生成代码比较小,运行度比较快,仅部分平台支持。
注意:提供共享库的同时也需要提供头文件。
2. 调用共享库
-
直接调用
# gcc main.c libmath.so
-
通过LIBRARY_PATH环境变量指定库路径
# export LIBRARY_PATH=$LIBRARY_PATH:. # gcc main.c -lmath (环境法)
-
通过gcc的-L选项指定库路径
# unset LIBRARY_PATH # gcc main.c -lmath -L. (参数法)
一般化的方法:gcc .c/.o -l<库名> -L<库路径>
3. 运行
运行时需要保证LD_LIBRARY_PATH,环境变量中包含共享库所在的路径。
在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中。
而只是将该函数在共享库中的地址嵌入到可执行程序中,因此运行时需要依赖共享库。
gcc缺省链接共享库,可通过-static选项强制链接静态库。
四、动态加载共享库
#include <dlfcn.h>
1. 加载共享库
void* dlopen (const char* filename, int flag);
返回值:
成功返回共享库句柄,失败返回NULL。
参数:
filename:共享库路径
若只给文件名,则根据LD_LIBRARY_PATH环境变量搜索。
flag取值:加载方式
RTLD_LAZY - 延迟加载,使用共享库中的符号 (如调用函数)时才加载。
RTLD_NOW - 立即加载。
2. 获取函数地址
void* dlsym (void* handle,const char* symbol );
返回值:
成功返回函数地址,失败返回NULL。
参数:
hanedle:共享库句柄
symbol:函数名
3. 卸载共享库
int dlclose (void* handle);
成功返回0,失败返回非零。
4. 获取错误信息
char* dlerror (void);
有错误发生则返回错误信息字符串指针,否则返回NULL。
范例:load.c
#include <stdio.h>
#include <dlfcn.h>
typedef int (*PFUNC_CALC) (int, int);
typedef void (*PFUNC_SHOW) (int, char, int, int);
int main (void)
{
void* handle = dlopen ("shared/libmath.so", RTLD_NOW);
if (! handle)
{
fprintf (stderr, "dlopen: %s\n", dlerror ());
return -1;
}
PFUNC_CALC add = (PFUNC_CALC)dlsym (handle, "add");
if (! add)
{
fprintf (stderr, "dlsym: %s\n", dlerror ());
return -1;
}
PFUNC_CALC sub = (PFUNC_CALC)dlsym (handle, "sub");
if (! sub)
{
fprintf (stderr, "dlsym: %s\n", dlerror ());
return -1;
}
PFUNC_SHOW show = (PFUNC_SHOW)dlsym (handle, "show");
if (! show)
{
fprintf (stderr, "dlsym: %s\n", dlerror ());
return -1;
}
show (30, '+', 20, add (30, 20));
show (30, '-', 20, sub (30, 20));
if (dlclose (handle))
{
fprintf (stderr, "dlclose: %s\n", dlerror ());
return -1;
}
return 0;
}
注意:链接时不再需要-lmath,但需要-ldl。
5. 静态链接库的优缺点
优点:
(1) 更加节省内存并减少页面交换。
(2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性。
(3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数。
(4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
缺点:
(1)使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败。
(2)速度比静态链接慢。
(3)当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件将无法运行。这在早期Windows中很常见。