在分析静态链接库、动态链接库之前,我们先来看一下计算机是怎么将.c文件一步步编译成可执行程序的,又是在哪一步使用到了静态库和动态库。
如下图,一个C代码变成可执行程序,主要运行在两个环境,即编译环境 和执行环境 。
在编译环境 里面又包含了编译过程 和链接过程 两步。
在编译过程 里面包含了预处理、编译、汇编 3步。
综上,就是我们常说的,C编译过程为:
预处理
编译
汇编
链接
预处理具体做的事情如下:
将所有的#define
删除,并且展开所有的宏定义。
处理所有的条件编译指令,#ifdef #ifndef #endif
等
处理#include
,将#include
指向的文件插入到该行处
删除所有注释
添加行号和文件标示。这样在调试和编译出错的时候才能定位问题
使用gcc -E main.c -o main.i
命令可以生成预编译之后的.i
文件。
编译具体做的事情如下:
词法分析
语法分析
语义分析
源代码转为汇编代码
使用gcc -S main.c -o main.s
或gcc -S main.i -o main.s
命令都可以生成编译后的.s
汇编文件。从这一步也可以看出main.c 和main.i 所表达的内容是相同。
使用gcc -c main.c -o main.o
命令,可以将汇编语言翻译成目标机器指令,生成.o
文件。
在实际开发中,我们一定是多文件编程,在汇编过后,单文件是不能工作的,需要多个文件相互结合起来,才能正常工作,这个结合的过程就叫做链接 。
上面讲到需要多个文件结合才能正常工作,这其中就包含了静态链接库和动态链接库。也就是说,动态链接库和静态链接库是在此处起作用的。
使用gcc main.c -o main
命令,可以生成最终的可执行程序。
静态链接方式 :在程序执行之前完成所有的组装工作,生成一个可执行的目标文件。windows下的库后缀为.lib
,Linux下的库后缀为.a
。
动态链接方式 :在程序已经为了执行被装入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝windows下的库后缀为.dll
,Linux下的库后缀为.so
。
综上 ,他们的一个主要区别在于链接的时机不同。
静态链接库 对应的链接方式为静态链接 。在链接阶段,会将汇编生成的目标.o
文件与引用的库(静态链接库)一起链接打包到可执行文件中。静态链接库有以下特点
静态库对函数库的链接是放在编译时期完成的。
程序在运行时与函数库再无联系,移植方便。
浪费空间和资源。因为所有相关的目标文件与牵涉到的函数库被链接合成了一个可执行文件。
此处,我们以Linux下使用静态链接库为例(Windows下同理),作简要介绍
注: Linux静态库命名规范,必须是lib[your_library_name].a :lib 为前缀,your_library_name 是静态链接库名,扩展名为.a
。
我们实现准备好了main.c、test1.c、test1.h
三个文件。
通过对C编译流程的分析,我们创建静态链接库有如下步骤
.o
文件gcc -c test1.c -o test1.o
.o
文件使用ar工具,生成静态链接库ar crv libtest1_lib.a test1.o
gcc main.c -o main -I ./ -L ./ -ltest1_lib
使用上述命令,可以生成一个基于静态链接库的test1_lib.a的main可执行程序。
其中
-I(大写的i)指定了头文件路径
-L(大写的L)指定了静态链接库的路径
-l(小写的L)指定了静态链接库名字
通过上述对静态链接库的分析,发现静态链接库的使用是简单的,也是很好理解的。但缺点 也是一目了然的,如
空间浪费严重。静态链接库在内存中存在多份拷贝的问题。
在程序的更新、部署、发布时,使用静态链接库会显得十分麻烦。
基于上述静态链接库的缺点,就有了动态链接库的使用。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入。
相对于静态链接库,优点 如下
不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。
动态库在程序运行时才被载入,解决了静态库对程序的更新、部署和发布也会带来麻烦。用户只需要更新动态库即可,可实现增量更新 。
可以通过显示调用 动态链接库的方式,实现库的链接由程序员在程序中自由控制。
注: Linux动态库命名规范,必须是lib[your_library_name].so :lib 为前缀,your_library_name 是动态链接库名,扩展名为.so
。
.c
文件生成.so
库,使用如下命令gcc -fPIC -shared test1.c -o libtest1_lib.so
其中
-fPIC
的作用为告诉编译器产生与位置无关代码。即产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的 。 PIC即Position-Independent Code的简写。
-shared
的作用为生成共享目标文件。
export LD_LIBRARY_PATH=$(pwd)
gcc main.c -o main -I ./ -L ./ -ltest1_lib
-I(大写的i)、-L(大写的L)、-l(小写的L)同上。
上述介绍的动态库使用方法,同静态库一样属于隐式调用,编译的时候需要指定相应的库和查找路径。其实,动态链接库还可以支持显示调用。
显示调用动态链接库,需要用到#include<dlfcn.h>
头文件,这个头文件里面包含了
void *
dlopen ( const char * pathname, int mode );
:函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。
void*
dlsym (void* handle,const char* symbol);
:dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。
int
dlclose (void *handle);
:dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
const char *dlerror(void);
:当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
同上,我们还是准备好了main.c、test1.c、test1.h
三个文件。并且已经通过test1.c文件生成了一个动态链接库libtest1_lib.so
,test1.c文件里面定义了一个void test1_fun(void)
函数,我们编写main.c
代码如下
#include "stdio.h"
#include <dlfcn.h>
void main()
{
void* handler = dlopen("./libtest1_lib.so",RTLD_NOW | RTLD_DEEPBIND);
if(dlerror() != NULL)
{
printf("%s",dlerror());
}
void(*test1_fun)()=dlsym(handler,"test1_fun");
if(dlerror()!=NULL)
{
printf("%s",dlerror());
}
test1_fun();
dlclose(handler);
}
第2行,引用了dlfcn头文件
第6行,以RTLD_NOW和RTLD_DEEPBIND方式打开了libtest1_lib.so库。
#define RTLD_LAZY 0x00001 /* Lazy function call binding. */
#define RTLD_NOW 0x00002 /* Immediate function call binding. */
#define RTLD_BINDING_MASK 0x3 /* Mask of binding time value. */
#define RTLD_NOLOAD 0x00004 /* Do not load the object. */
#define RTLD_DEEPBIND 0x00008 /* Use deep binding. */
第13行获取libtest1_lib.so库中test1_fun函数的地址
第19行,运行test1_fun函数
第21行,关闭libtest1_lib.so库。
关注,回复【1024】海量Linux资料赠送
精彩文章合集
linux入门