linux下生成静态库和动态库
一、动态库、静态库简介
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:
静态库.a(win 系统下是lib)和动态库.so(win 系统下是.dll)。所谓静态、动态是指链接。回顾一下,将一个程序编译成可执行程序的步骤:
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
静态库特点总结:
-静态库对函数库的链接是放在编译时期完成的。
-程序在运行时与函数库再无瓜葛,移植方便。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
动态库特点总结:
-动态库把对一些库函数的链接载入推迟到程序运行的时期。
-可以实现进程之间的资源共享。(因此动态库也称为共享库)
-将一些程序升级变得简单。
-甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
本文主要通过举例来说明在 Linux 中如何创建静态库和动态库,以及使用它们。
[ps:在windows平台和linux平台下都大量存在着库。由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。本文仅限于介绍 linux 下的库。]
二、用gcc生成静态和动态链接库的示例
首先,本文主要通过举例来说明在 Linux 中如何创建静态库和动态库,以及使用它们。为了便于阐述,我们先准备下测试代码:hello.h、hello.c、main.c(我将这三个代码放在一个名为‘静态链接’的文件夹中)。如下所示
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char* name);
#endif
hello.c
#include
void hello(const char* name){
printf("hello %s! \n",name);
}
main.c
#include "hello.h"
int main(){
hello("everyone");
return 0;
}
注意:这个时候,hello.c 是无法通过gcc –o 编译,这个道理非常简单,hello.c 是一个没有main函数的.c程序,因此不够成一个完整的程序,如果使用gcc –o编译并连接它,gcc 将报错。
(ps:gcc -o 与gcc -c 的区别。gcc–o 是将.c源文件编译成为一个可执行的二进制代码,这包括调用作为 GCC 内的一部分真正的 C 编译器( ccl ),以及调用GNU C编译器的输出中实际可执行代码的外部 GNU 汇编器和连接器工具。而g -c是使用GNU汇编器将源文件转化为目标代码之后就结束,在这种情况下连接器并没有被执行,所以输出的目标文件不会包含作为 Linux 程序在被装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序)
其实,我们的问题就是,如何让main.c中能使用hello这个函数。这时候我们有三种思路:
1、通过编译多个源文件,直接将目标代码合成一个.o 文件。
2、 通过创建静态链接库libmyhello.a ,使得 main 函数调用 hello 函数时可调用静态链接库。
3、 通过创建动态链接库libmyhello.so ,使得 main 函数调用 hello 函数时可调用静态链接库。
下面分别进行演示。
1.通过编译多个源文件,直接将目标代码合成一个.o 文件.首先利用gcc -c指令将hello.c和main.c编译生成hello.o和main.o文件,再用gcc -o指令将hello.o和main.o文件生成一个可执行文件hello。具体如下图所示:
2.通过创建静态链接库libmyhello.a ,使得main函数调用hello函数时可调用静态链接库。
下面我们先来看看如何创建静态库,以及使用它。Linux静态库命名规范,必须是"lib[your_library_name].a":lib为前缀,中间
是静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello ,静态库文件名就是 libmyhello.a。在创建和使用静态库时,
需要注意这点。Linux创建静态库分为两步:
第一步:将代码文件编译成目标文件。即将hello.c文件编译成hello.o文件,使用指令是:gcc -c hello.c
第二步:利用ar工具将目标文件hello.o打包成静态库文件.a(注意命名规则,我的静态库文件名为:libmyhello.a)
现在我们已经生成了静态库文件libmyhello.a了,下面就是怎么使用它。Linux下使用静态库,只需要在编译的时候,指定静态库的
搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,-l选项)。
指令:
gcc -o hello main.c -L/Users/wangxuewei/Desktop/静态链接 -lmyhello
指令可以这么解释:利益gcc -o指令,将main.c编译成可执行文件hello。其中main.c函数中要使用的函数,存储在静态链接库
libmyhello.a 。"-L/Users/wangxuewei/Desktop/静态链接"是该静态库文件所在地址,"-lmyhello"表示的是要使用的静态库文
件的名字,看起来怪怪的哈,因为它不需要制定前缀lib和后缀.a,所以就变成了myhello,-l则是指令标示符号。
[ps:关于gcc指令中的-l和-L参数问题。-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?就拿数学库来说,他的库名是m,他的库文件名是libm.so(.o),很容易看出,把库文件名的头lib和尾.so/.o去掉就是库名了。放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了,但如果库文件没放在这三个目录里,而是放在其他目录里,这时我们只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find -lxxx”,也就是链接程序ld在那3个目录里找不到libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库,它在/usr/X11R6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -lX11参数,-L参数跟着的是库文件所在的目录名。再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L/aaa/bbb/ccc -ltest]
进一步可以删除静态函数libmyhello.a,然后运行hello函数,看看其是否真的链接到了目标函数.
3、 通过创建动态链接库libmyhello.so ,使得 main 函数调用 hello 函数时可调用静态链接库。
动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为“.so”。
l 针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
l 在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。
创建动态库(.so)可以分为两步:
第一步:生成目标文件,此时要加编译器选项-fPIC。[ps:-fPIC是创建与地质无关的编译程序,是为了能够在多个应用程序间共享]
gcc -fPIC -c hello.c
第二步:生成动态库,此时要加链接器选项-shared。[ps:-shared是指定生成动态链接库]
gcc -shared -o libmyhello.so hello.o
以上两步也可以写成一条指令:
gcc -shared -fPCI -o libmyhello.so hello.c
运行该指令后会生成一个libmyhello.so文件,如下图:
使用动态链接库
gcc main.c -L. -lmyhello
-L. 代表动态库在当前路径下;-l后面跟的是动态链接库的名字(
与静态一样,可以舍去前缀lib和后缀.so)
执行该指令后,会生成一个a.out的可执行文件,可以执行下看看:
可以看到,是可以执行的!!但是,在网上看,很多博客说这样是不可以的,因为:库文件在连接(静态库和动态库)和运行(仅限于使用动态库的程序)时被使用,其搜索路径是在系统中进行设置的。一般Linux系统把/lib 和 /usr/lib(也可能是/usr/local/lib)三个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到搜索路径之中。但是,我电脑中,当动态库与执行程序在同一目录下时,也可以运行,可能是因为我用的mac吧,暂时没弄清楚。。。不过可以确定,当将动态库libmyhello.so移动到/usr/local/lib时,a.out仍可正常运行。
更一般的,对于处于默认路径之外的库,如何让系统找到它呢?一般有两种方法:
1.将动态库复制到/lib 或 /usr/lib 或 /usr/local/lib中。就像我上面做的一样,将libmyhello.so复制到/usr/local/lib中,这是系统是可以找到的。
2.如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:
2.1 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
2.2 运行ldconfig ,该命令会重建/etc/ld.so.cache文件