您的位置:首页 > 运维架构 > Linux

linux下动态与静态链接库的使用及区别

2015-11-02 09:56 399 查看

1.为什么要有链接库

我们平时在写C代码时一定会经常重复写某些很多程序中都经常用到的代码,比方说字符串的简单操作函数,或者链表等。那么我们如何能避免老是重复的去写这些基础的代码呢?也许你会不假思索的想到把这些常用的函数装到一个头文件里,调用的时候只要include该头文件不就可以么。的确这样做是可以达到目的,但是如果我们把许多种不同功能的操作代码(可以理解为一个函数)都放在一个头文件下,数量少了还好,如果数量有几十甚至上百时,这么多功能不同的函数组合在一个头文件里,我想也许这个头文件只会你一个人去看它,因为其他人根本不知道里面写的是啥。此时你又会想,将这写函数按功能装在不同的头文件里不就解决了上述问题。问题到这足以证明你有一颗灵活的大脑,哈哈。但话说回来我们一般都在头文件里只定义函数,函数的具体实现都会另起一个文件里去实现

例如:

我们在hello.h中定义如下

#ifndef HELLO_H
#define HELLO_H
//此函数功能实现打印Hello xxx
void hello(char *s);
#endif


然后我们另起一个名为hello.c的文件用于实现hello函数

void hello(char *s)
{
printf("Hello %s\n",s);
}


之所以规范要把头文件定义的函数和实现分离,主要原因是供使用头文件的人阅读起头文件来简单且方便。因为可能使用你头文件的人根本不关心你的某个函数内部是如何如何用了几千行代码实现的某个功能,它只关心某个函数是用来干什么的如何来传参数使用就足够了,所以如果你把头文件中的函数和实现放在一起的话,如果函数实现就几行还可接受,如果它的实现代码量特别大的话,显然会对于上述使用者看头文件带来一定的复杂度,而且这样写我想你的代码结构也不会很好。到这里我想我已把为什么要用头文件以及为什么要把头文件里的函数和实现分文件写说清楚明了了。既然上述方法已实现了我们刚开始讨论的遇到的问题,那么为什么还要谈动态或静态链接库呢?

对此我们在回到上述头文件与其函数分开实现在不同文件的问题,我们可以想想一下假如我们的一个主程序中用到了超过10自己实现的.h文件,那么对应就会有10个.c文件,此时你会如何编译

如果你是新手你也许会这样吧

gcc main.c 1.c 2.c 3.c ... 10.c


很明显这样变一起来太繁琐了

或许你比较聪明使用makefile来管理你的编译,然后make一键编译,哦,乍一看好像解决了上述编译的繁琐问题,但其实这还是治标不治本,你的makefile文件还得你自己来写把,那么我们在换个主程序,你的makefile是不又得重新编译?好了此时我们的静态或动态链接库来帮助你解决麻烦,链接库也正是好多第三方库使用的方法,如我们在编译线程相关的函数是得加-lpthread,使用libevent时后面加levent使用连接mysql时在后面加 -lmysql等

2.链接库的功能

其实我想大家如果认真看完上述问题,到这里因该已经基本上知道链  接库能帮我们干什么了吧
链接库说白了其实就是把我们上述所说的繁多的用于实现头文件的一系列.c文件放在由我们命名的一个文件库中,当我们编译时就不会像上面的例子一样那么繁琐了,假如我们给上面的1.c-10.c先生成.o文件然后将其都放在名为libhello.a的链接文件中,那么我们编译的时候可以直接如下


gcc main.c -lhello


是不是瞬间感觉到了链接库的存在,哈哈

3.定义自己的链接库

1)静态链接库

在linux环境中, 使用ar命令创建静态库文件.如下是命令的选项:

d —–从指定的静态库文件中删除文件

m —–把文件移动到指定的静态库文件中

p —–把静态库文件中指定的文件输出到标准输出

q —–快速地把文件追加到静态库文件中

r —–把文件插入到静态库文件中

t —–显示静态库文件中文件的列表

x —–从静态库文件中提取文件

还有多个修饰符修改以上基本选项,详细请man ar 以下列出三个:

a —–把新的目标文件(*.o)添加到静态库文件中现有文件之后

b —–[b]*****************************[/b]之前

v —–使用详细模式

ar的详细说明请读者自行 man ar就可获得

ar 命令的命令行格式如下:

ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...


参数archive定义库的名称, files是库文件中包含的目标文件的清单, 用空格分隔每个文件. ar与archive中间的为上述的命令选项

接下来我们就以下面的三个文件为例

hello.h

#ifndef HELLO_H
#define HELLO_H
//此函数功能实现打印Hello xxx
void hello(char *s);
#endif


hello.c

void hello(char *s)
{
printf("Hello %s\n",s);
}


main.c

#include "hello.c"  //hello.c得在你当前目录下
int main(int argc,char **argv)
{
hello("shreck");
return 0;
}


接下来就开始生成我们自己的静态链接库

(1)将hello.c编译生成目标.o文件

gcc -c hello.c


此时我们ls一下当前目录如下



可以看到已生成了hello.o文件

(2)创建名字为libhello.a的静态链接库

ar cqs libmyhello.a hello.o //将.o文件放入名为libmyhello.a的链接库中


上述命令结束后ls当前目录会发现libhello.a文件生成



(4)在主程序中使用静态库

如上我们的libhello.a的静态库文件以生成,那么我们如何使用到它的内部函数呢?

.首先我们要在使用这些公共函数的源程序中包含这些公用函数的原型声明(头文件里,所以只需包含头文件即可)
.接着在用gcc命令编译时指明静态库名,gcc就会从静态库中将公用函数连接到目标文件中,需要注意的是gcc会自动在静态库名前加一个前缀lib,和扩展名.从而得到扩展名为.a的静态库文件名来查找静态库文件


实例如下

gcc main.c -L. -lmyhello


编译完成后我们的当前目录下运行./a.out结果如下



其中-L.是用来显示指出动态链接库在当前目录下,用户程序的链接库一般存在/user/lib中,所以如果我们不想每次编译时都加-L.参数的话就可以把生成的动态链接库放在/user/lib下,这样我们编译时只需这样

gcc main.c -lmyhello


(5)删除静态库文件看程序还能否正确运行

我们将当前目录下刚生成的libhello.a文件删除(注意/user/lib下也不能有该文件别忘了),然后运行程序,结果如下



程序正常运行,由此我们可以得出静态链接文件是在编译时就连接进来了,这一点与动态链接明显不同

2)动态链接库

还是用上面的hello.h,hello.c ,main.c三个文件

(1)将hello.c编译生成hello.o文件

gcc -c -fPic hello.c


由于我的是amd64位机所以得加-fPIC参数,运行结果如下



(2)由.o文件创建动态库文件

命令如下

gcc -shared -fPCI -o libmyhello.so hello.o


需要注意的是用-shared参数来生成动态链接库,加-fPCI是因为我的是64位机,libmyhello.so为动态库的名字(注意扩展名为.so),hello.o为往该库加的文件,可以是多个文件,命令执行后会生成libmyhello.so文件,ls当前目录查看结果为



(3)在程序中使用动态库

我们可以向静态库一样编译我们的main.c具体如下

gcc main.c -L. -lmyhello


运行./a.out结果如下



从截图中可以看出提示我们不能打开libmyhello.so共享文件,那是因为在运行时程序会在/user/lib下找libmyhello.so文件进行动态加载,所以我们因该把本地目录下的libmyhello.so拷贝到/user/lib中,然后在运行我们的程序如下



此时程序正常运行

(4)删除动态链接库

其实(3)中发生的错误实质上已经说明了删除动态链接库的后果,由于动态链接库实在程序运行时才加载的(静态库是在编译时就加载)所以一旦删除动态库,那么程序在运行时将会报错说找不到某动态库如(3)中所示的错误一样,大家不妨试试,这里我就不在赘余了

4.常见的链接库

平时我们遇到的链接库其实也挺多的,例如我们在编译带有线程库的函数时得在后面加-lpthread,编译使用了libevent库的文件时,得在后面加上-levent,还有编译json或异步I/O等时都得在编译时都得加上特有的链接库

需要声明的一点是,为了在讲解链接库时能够让大家理解起来更容易些,所以我一直都只说其针对c语言以及c的函数什么的,其实动态库被几乎大部分语言所使用,每个语言几乎都有自己的第三方库,所以对应的也基本都会用到链接库
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 linux 函数