目录
在一个大型的项目中,我们可以使用别人写的库来进行操作,通常情况下,这些库中并没有源文件,而是只有头文件和库本身,本文将详细阐述一个静态库或者动态库的制作过程和使用方法。
一、库的概念
1.库是一个二进制可执行的文件;(存储功能函数)
2.库需要被载入到内存中使用
3.比较于二进制程序,库是不能单独运行的
4.每个操作系统都有自己的库,不兼容。
二、库的分类
库分为动态库和静态库。
动态库的后缀:在Linux下后缀为.so,在win系统下后缀为.dll。
静态库的后缀:在Linux下后缀为.a,在win系统下后缀为.lib。
1.动态库
动态库是一个可由多个程序同时使用的代码和数据的库,动态库不是可执行文件。动态链接提供了一种方法,是进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个动态库中,该动态库包含一个或多个已被编译、链接并使用它们的进程分开存储的函数。
2.静态库
静态库是指在我们的应用中,有一些代码是需要反复使用,就把这些代码编译为"库"文件;在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中的这种库。
总结来说,当可执行程序使用动态库的时候,并不将动态库中已经编译的内容加载到可执行程序中,而是直接去动态库中找。而如果使用静态库,则会将静态库中的已经编译的内容加载到可执行程序中。其实,所谓动态库和静态库本质上就是.o文件的集合
三、查看所依赖的动静态库
使用ldd指令进行查看程序所依赖的动态库:
我们写一个简单的C语言程序test.c,并生成可执行未见mytest。
gcc test.c -o mytest
ldd mytest
此时我们就可以看到,该可执行程序所依赖的库位动态库,libc.so.6
而动态库真正的名称是将lib和后缀去掉,即c,它是从标准库。
同时我们可以知道,在默认的情况下C语言程序编译依赖的是动态库。
我们可以使用static选项来指定C语言程序依赖静态库来进行编译:
gcc test.c -o mytest1 -static
此时再使用ldd查看mytest1所依赖的库的时候,没有找到任何它依赖的动态库,说明它所使用的是静态库。同时我们发现mytest1比mytest明显要大很多,这是因为mytest1中被直接导入了静态库的内容。
同时我们还可以在bin目录下查看系统所有的动态库和静态库:
四、制作、发布与使用静态库
1.制作并发布静态库
一个完整的库不仅仅包含二进制文件,还包含头文件。头文件的作用是告诉用户,库中都为用户提供了什么方法。
系统自带的头文件一般在路径:usr/include下。
同时,我们不会将源文件放在库中,这样就保证了库的私密性。其实制作过程就是将.o文件以及头文件进行打包。
我们可以写一个简单的计算器来说明这一过程:
//sub.h
#pragma once
#include<stdio.h>
int my_sub(int x,int y);
//add.h
#pragma once
#include<stdio.h>
int my_add(int x,int y);
//sub.c
#include "sub.h"
int my_sub(int x,int y)
{
return x-y;
}
//add.c
#include "add.h"
int my_add(int x,int y)
{
return x+y;
}
//mytest.c
#include"./test_lib/sub.h"
#include"./test_lib/add.h"
int main()
{
int x=10;
int y=20;
int r1=my_add(x,y);
int r2=my_sub(x,y);
printf("ADD:%d,SUB:%d",r1,r2);
}
我们建立一个Makefile文件来实现以上的过程:
libmymath.a:sub.o add.o
ar -rc $@ $^ //将sub.o与add.o打包起来,其中ar为归档命令,-rc表示没有则create,有则进行replace
%.o:%.c
gcc -c $< //通过自动推导,将所有.c文件生成.o文件
.PHONY:clean
clean:
rm -f *.o libmymath.a //删除打包的库
.PHONY:output
output:
mkdir output
cp -rf *.h output
cp libmymath.a output
cp *.h output //将库和头文件合成一个完整的库
此时目录下的output就是我们要进行发布的库
2.使用静态库
所谓使用动态库,就是我们拿到了上文中的output文件夹,该如何使用它。
在我们写好的C语言程序中只需要包含库output中的头文件:
#include"sub.h"
#include"add.h"
int main()
{
int x=10;
int y=20;
int r1=my_add(x,y);
int r2=my_sub(x,y);
printf("ADD:%d,SUB:%d",r1,r2);
}
并在在生成可执行程序的时候加上如下选项:
gcc -I./output -L./output -lmymath
其中-I后的内容表示的是头文件的路径,-L.后的内容表示的是库的路径,-l后表示的是库的名称。(注意是去掉lib以及后缀之后的名称)
其中前一个是大写的i,后一个是小写的L。在英文输入法中这俩货太像了。
执行以上语句我们就可以得到一个可执行程序a.out,运行得到我们想要的结果:
那么为什么我们在调用正常的C语言的程序时,包含了头文件而没有加-I,-L,-l这些选项呢?这是因为系统的库和头文件都被放在了特定的目录下,系统需要调用方库中方法时自动会去寻找该库以及头文件。
换句话说,如果我们不想带这些选项,我可以把对应的库和头文件拷贝到默认的路径下,但是这种做法严重不推荐!会造成污染。
而上面的过程其实就是一般软件的安装过程。
五、制作、发布与使用动态库
1.制作并发布动态库
动态库的特点就是,库可以加载到内存的任何位置,不影响其他程序的关联性。
只需要在静态库的Makefile基础上添加一些选项即可:
libmymath.so:add.o sub.o
gcc -shared -o $@ $^
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -f libmymath.so
.PHONY:lib
lib:
mkdir lib
cp *.h lib
cp libmymath.so lib
此时我们就得到了一个完整的动态库lib
2.使用动态库
使用动态库与使用静态库的方法类似,只不过比静态库多加了一个加载器,它的目的是告诉系统库在哪里:
此时我们还使用静态库的方式进行编译会找不到库,因为我们仅仅告诉了编译器库在哪里,而没有告诉系统库在哪里。
告诉系统库在哪里的方式有三种:
1.我们可以把动态库拷贝到系统的路径下。
这种方式严重不推荐,因为可能会造成污染。
2.还可以在环境变量LD_LIBRARY_PATH这一环境变量中加入库的路径,我们推荐这种做法:
我们可以查看一个该环境变量。
echo LD_LIBRARY_PATH
只需要将库的路径导入到其中即可:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib
在导入环境变量之后再执行gcc的操作则可以运行成功。
3.还可以使用Idconfig 配置/ect/ld.so.conf.d/,并对ldconfig进行更新。
可以先通过/etc/ld.so.conf.d/路径下查找已经链接的库,然后向其中添加conf文件:
touch bit.conf
然后将库的路径写入到conf文件中。并进行库的路径更新:
ldconfig
六、总结
本文主要讲了关于动静态库的两部分内容,即动静态库的使用与制作。对于动静态库的使用指的是拿到别人写好的动静态库如何加入到自己的项目中来。关于动静态库的制作分为三个步骤:首先将自己所有的源文件编译成.o文件,然后将所有的.o文件进行打包,静态库使用ar -rc来进行打包,动态库使用gcc shared来进行打包。最后将.a和.so文件和头文件包含在一起最后形成一个完整的动静态库。