一、首先我们来说一下库的基础概念:
在windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windows和linux的本质不同,因此二者库的二进制是不兼容的。通俗的说就是把这些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用。在使用函数时,只需要包对应的头文件即可。按照库的使用方式又可分为动态库和静态库,在不同平台下对应后缀也有所不同。
作为一名程序员,对于编译链接的过程要了然于胸。
- 首先大概介绍一下,编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;
- 然后进行编译,这个过程主要是进行词法分析、语法分析和语义分析等,生成*.s的汇编文件;
- 最后进行汇编,这个过程比较简单,就是将对应的汇编指令翻译成机器指令,生成可重定位的二进制目标文件。
- 以上就是编译的过程,下面主要介绍两种链接方式–静态链接和动态链接。
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时,下面来详细介绍这两种链接方式。
WINDOWS下:.dll 后缀为动态库,.lib 后缀为静态库; LINUX下:.so后缀为动态库,.a后缀为静态库。 |
比较 | 静态库 | 动态库 |
---|---|---|
概念 | 静态链接库就是一个多个汇编文件(obj文件)的集合,在Linux中通常命名为libxxx.a。对于静态链接库函数的连接是在编译阶段,直接将obj文件的一份拷贝代码连接到目标文件。 | 动态链接库也是一个或多个汇编文件的集合,在Linux中通常命名为libxxx.so。对于动态链接库函数的链接是在运行阶段,各个目标程序之间共享同一份库文件。在编译阶段仅仅加载了函数符号。 |
优点 | 在编译阶段就完成连接,不需要单独库文件。(简单、直接、粗暴)。 | 1) 代码共享,当多个程序运行同一个动态库的时候,其实他们是共享的同一份代码。减少了内存的使用量。2)按需加载,只有当运行过程中需要使用动态库的函数,才会去加载动态库。减少了不必要的内存消耗。 |
缺点 | 1) 由于每个目标程序都需要拷贝一份连接代码,所以会加重内存的负载。 | 1) 由于是在运行阶段,才进行加载,所有会降低目标代码的执行速度。 |
- 需要注意一点:由于代码共享,所以动态库在编译的时候,内部需要使用相对地址。
下面我用代码来展示整个静态库和动态库的链接过程
静态链接:
一:文件准备过程
一个头文件如下:
- staticlib.h
//array operations
int max(int *,int);
int sum(int *,int);
//initial array
void initarray(int *,int);
void showarray(int *,int);
三个c文件如下:
- aoprand.c//一共有两个函数,一个为计算最大值,另一个为计算总和
int max(int *a,int n){
int m,i;
m = a[0];
for(i=0;i<n;i++)
if(a[i]>m)
m=a[i];
return m;
}
int sum(int *a,int n){
int s,i;
s=0;
for(i=0;i<n;i++)
s+=a[i];
return s;
}
- base.c//一共两个函数,一个循环遍历数组元素,另一个生成一个随机数组。
#include<stdio.h>
void showarray(int *a,int n){
int i;
for(i=0;i<n;i++)
printf("%4d : %4d\n",i,a[i]);
}
void initarray(int *a,int n){
int i=0;
srand((unsigned)time(NULL));
for(i=0;i<n;i++){
a[i] = rand()%100;
}
}
- localtest.c//该函数为测试函数
#include"staticlib.h"
#include<stdio.h>
int main(){
int a[5];
initarray(a,5);
showarray(a,5);
printf("max = %4d\n",max(a,5));
printf("sum = %4d\n",sum(a,5));
return 0;
}
二:现在文件都准备好了,开始展示静态链接指令。
- 在当前目录下对文件进行链接
- 文件如下:
- 文件如下:
- 对源文件进行编译生成.o的目标文件
- ar -rc libstatic.a aoprand.o base.o//对aoprand.o和base.o两个文件进行链接生成libstatic.a静态库文件
- gcc localtest.c -o localtest -L…/staticlib/ -lstatic (-L参数是在指定的目录下寻找.a文件(我的指定目录是staticlib),-l*** 其中***是指静态库的名称,比如说,如下图生成的静态库全称为libstatic.a,其中static是静态库的名称。)
- 在另外一个目录下进行库文件的链接
- 文件结构如下:(其中remotetest.c就是上文中的localtest.c,也即测试文件。)
- 文件结构如下:(其中remotetest.c就是上文中的localtest.c,也即测试文件。)
- 链接指令如下:
- gcc remotetest.c -o remotetest1 -L…/staticlib/ -lstatic -I…/staticlib/
其中-I(大写的i)是到指定的目录下去寻找头文件
- gcc remotetest.c -o remotetest1 -L…/staticlib/ -lstatic -I…/staticlib/
#include"/home/liaozhiwen/gitspace/week5/code/staticlib/staticlib.h"
- 也可以在函数体内导入头文件,需要指明头文件的路径。
#include"/home/liaozhiwen/gitspace/week5/code/staticlib/staticlib.h"
动态链接
- 当前目录下进行动态链接
- 生成动态库指令:gcc -shared -fPIC -o libdylib.so aopend.c base.c
- 下面这个使用的是gcc ldtextlocal.c -o ldtextlocal -L…/dynamiclib/ -ldylib -I…/staticlib(这条指令是我们仿照静态链接的指令,结果这条指令执行的时候没有报错,但是执行./ldtextlocal文件的时候出错了。)
- 在当前目录下链接当前动态库:gcc ldtextlocal.c -o ldtextlocal ./libdylib.so(这样就不会有问题了)
- share代表是动态链接库
- fPIC命令行标记告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样编译出的hello.o可以被用于建立共享链接库。
- 在其他目录下链接动态库有两种方式
- 文件结构如下:(其中dytestremote.c与上文的localtest.c文件和ldtextlocal.c文件都是一样的)
- 动态链接(程序运行一开始就要加载库)
- 动态加载时链接(动态加载库是程序在运行到函数需要这个函数的实现的时候才加载库。要在测试文件中修改内容,即主函数)
- dytestrt.c文件内容如下:
- 文件结构如下:(其中dytestremote.c与上文的localtest.c文件和ldtextlocal.c文件都是一样的)
#include"dylib.h"
#include<stdio.h>
#include<stdlib.h>
#include<dlfcn.h>
int main(){
void *hd;
void (*f1)();
int (*f2)(),(*f3)(),(*f4)();
char *error;
int a[5];
hd = dlopen("../dynamiclib/libdylib.so",RTLD_LAZY);
if(!hd){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
f1 = dlsym(hd,"initarray");
if((error=dlerror())!=NULL){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
f2 = dlsym(hd,"max");
if((error=dlerror())!=NULL){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
- 执行的命令如下:gcc -rdynamic -o rttest dytestrt.c -ldl -I…/dynamiclib/