目录
写在前面的话
如何编写静态库库
编写静态库
ar命令
Makefile自动化形成静态库
如何使用编写的静态库
1.拷贝到系统路径中
2.指定路径搜索
如何编写动态库
编写动态库
完善Makefile
如何使用编写的动态库
指定路径搜索(不可行及原因)
环境变量LD_LIBRARY_PATH
修改配置文件
本文章主要讲解了动静态库的编写以及使用。在了解静态链接和动态链接的基础上,观看本文的效果会更好。
欢迎阅读我之前写的:动静态链接,里面有关于动态和静态链接的详细介绍,而且也有对动静态库的一些基本认识,欢迎阅读哦.
这里我们将编写两个类型的库:动态库(.so) 和 静态库(.a).
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库.
需要说明:库里不能包含main函数,因为是要别人用的。写main函数的话就会和别人的main函数冲突了.
我现在分别创建了4个文件:mymath.h mymath.c myprint.h myprint.c.
分别写入以下内容:
- mymath.h:
#pragma once #include
extern int addToTarget(int start, int end);//声明函数
- mymath.c
#include "mymath.h" # //这段函数的作用是计算start-end之间的和 int addToTarget(int start, int end) { int sum = 0; int i = start; for(i; i <= end; i++) { sum += i; } return sum; }
- myprint.h
#pragma once #include
#include extern void Print(const char* str);
- myprint.c
#include "myprint.h" //这段函数功能是输出传入的字符串,后面加上时间戳 void Print(const char* str) { printf("%s[%d]\n",str,(int)(time(NULL))); }
然后我们编写一个main.c文件,来调用这些函数.
#include "myprint.h"
#include "mymath.h"
int main()
{
Print("hello,world");
int res = addToTarget(1,100);
printf("res: %d\n",res);
return 0;
}
指令编译所有.c文件,目标生成my.exe可执行文件:
gcc main.c mymath.c myprint.c -o my.exe
然后我们执行my.exe文件,得到了我们预期的结果:
这只是我们的正常使用,毕竟源代码.c文件就在我们手上。
当我们把.h文件和 .c文件编译成的.o文件,给别人,别人可以用吗?
答案是可以的,我们首先把两个.c文件编译成.o文件:
gcc -o myprint.o -c myprint.c
gcc -o mymath.o -c mymath.c
然后我们换到另一个文件中,把两个.o和两个.h文件复制到这个文件中,然后将main.c也编译成main.o文件,此时我们将这三个.o文件一起编译,形成可执行程序。
gcc main.o mymath.o myprint.o -o my.exe
我们发现照样可以成功运行:
但问题是我们这里有太多的.o文件,发给别人也不方便,而且容易丢失,编译的时候还得都写上,这也未必太麻烦了。
所以此时我们需要把这些.o文件打包,这个打包的过程就是形成静态库的过程.
把这些.o文件打包需要用到ar命令,命令格式如下:
ar -[选项] lib+库文件名+.a 所有.o文件
选项这里只说r(replace替换),c(create创建)就可以了,就足以让我们创建静态库了.
其中需要注意的是:要形成的静态库的名字 前缀必须是lib,后缀必须是.a,中间可以随便起名字.
比如我想把所有的.o文件打包成名字叫hello的静态库,指令如下:
ar -rc libhello.a main.o myprint.o mymath.o
这样就形成了.
有了以上的认知,我们便对一个文件形成静态库的过程有了大概的了解:
.c文件 ---> .o文件 ---> 打包形成静态库
既然是这么一套固定的流程,那么我们完全可以用Makefile来完成这些工作。需要注意的是搞清各个文件的依赖关系,然后再进行编写。所以最后的Makefile编写如下:
libhello.a: mymath.o myprint.o
ar -rc libhello.a mymath.o myprint.o
mymath.o : mymath.c
gcc -o mymath.o -c mymath.c
myprint.o: myprint.c
gcc -o myprint.o -c myprint.c
.PHONY:clean
clean:
rm -rf *.o libhello.a
这样,我们不需要再手动编译了,直接使用make就好了.
但这样只是将.o文件打包了,还有.h文件,就是通常#include<>的那些,一般都是.h文件
.h声明,然后从.o打包形成的库里面找
所以我们还需要做一件工作:将.h和.o文件规整到一起.在Makefile加入以内容:
.PHONY:hello
hello:
mkdir -p hello/lib
mkdir -p hello/include
cp -rf *.h hello/include
cp -rf *.a hello/lib
即创建了三个目录,hello,lib和include,把当前所有的.h文件放到include文件夹中,所有.a库文件放到lib文件夹中。
这里首先把资源全部清理一下,然后make编译形成.o文件,再make hello 形成hello目录,里面包含了两个目录include 和 lib ,分别保存.h文件和库文件.
头文件gcc系统默认搜索路径是:/usr/include
库文件gcc系统默认搜索路径是:/lib64 或者 /usr/lib64
sudo cp hello/include/*.h /usr/include/ //拷贝头文件
sudo cp hello/lib/*.a /lib64 //拷贝库文件
拷贝完成后,我们再次编译main.c
发现还是不行,这是由于我们平常使用的是系统自带的库,而我们编写的是第三方库,所以我们编译时需要指定链接哪一个库.
gcc main.c -lhello
注意,-l后面直接加库的名字,不加前缀lib和后缀.a.
此时我们再编译,便运行成功了.
这种把自己的库拷贝到系统路径下的行为就叫做库的安装.
但是一般不建议采用这种办法。会污染别人已经写好的库。
我们可以直接在编译的时候加上头文件和库文件的路径.
gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello
选项:-I(i的大写)表示头文件的搜索路径
-L表示库文件的搜索路径
-l(小写的L)表示在特定路径下,要使用哪一个库.
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
同样地,我么也需要先将.c文件编译成.o文件,但此时需要加上选项 -fPIC.
gcc -fPIC -c myprint.c -o myprint.o
gcc -fPIC -c mymath.c -o mymath.o
选项
-fPIC
是指生成位置独立代码(Position Independent Code,PIC)。它通常用于创建可在不同内存地址空间加载的共享库(动态链接库)。具体来说,
-fPIC
选项的作用是告诉编译器生成与位置无关的代码,这些代码可以在内存中的任何位置加载和执行,与共享库的加载地址无关。这是通过使用相对偏移而不是绝对地址来访问全局变量和函数等的。
看不懂没有关系,我们后面会继续细说的.
然后我们需要将这些.o文件它打包成动态库.它没有单独的命令,使用gcc进行打包,但是需要加上 -shared的选项,如下:
gcc -shared myprint.o mymath.o -o libhello.so
这样我们就完成了动态库的打包和编写.
由于流程是固定的,所以我们依然可以使用Makefile来完成。
首先我们需要先将.c编译诚.o文件,并打包成动态库
然后需要将生成的动态库也放到我们之前创建的lib目录中。
完善后的效果如下(框起来的是新增的内容):
.PHONY:all
all:libhello.so libhello.a
libhello.so:mymath_d.o myprint_d.o
gcc -shared mymath_d.o myprint_d.o -o libhello.so
mymath_d.o:mymath.c
gcc -c -fPIC mymath.c -o mymath_d.o
myprint_d.o:myprint.c
gcc -c -fPIC myprint.c -o myprint_d.o
libhello.a: mymath.o myprint.o
ar -rc libhello.a mymath.o myprint.o
mymath.o : mymath.c
gcc -o mymath.o -c mymath.c
myprint.o: myprint.c
gcc -o myprint.o -c myprint.c
.PHONY:output
output:
mkdir -p hello/lib
mkdir -p hello/include
cp -rf *.h hello/include
cp -rf *.a hello/lib
cp -rf *.so hello/lib
.PHONY:clean
clean:
rm -rf *.o libhello.a output
此时我们即可以形成动态库,也可以形成静态库.
然后此时我们make编译,再make output将文件拷贝到指定目录中.
此时便把动态库也成功放入到lib路径下了.
此时我们便可以利用tar指令打包发送到网上,然后让别人使用了。
首先,静态库中的第一种拷贝到系统路径里这种方法一定是可行的.
第二种方法,这条指令:
gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello
我们首先要知道,gcc默认使用的是动态链接,即使用动态库.
a.当库中只有静态库时,gcc就只能针对该库进行静态链接.
b.当动静态库同时存在时,默认使用的就是动态库
c.如果动静态库同时存在,强制使用静态库,那就需要再上面指令后面加上 -static
表示的是:摒弃优先使用动态库的原则,而是直接使用静态库的方法.
代码如下:
gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello -static
我们同样地利用上面那一条指令进行编译生成可执行程序后,我们把可执行程序移动到上一级目录.然后运行它。
此时我们再运行,发现报错了,就是找不到这个共享的文件,这是为什么呢、我们不是已经指定路径了吗,为什么还找不到呢?
我们要明白,动态库是一个独立的文件,动态库可以和可执行程序分批加载!
而我们指定路径是给gcc指定查找的,是告诉了gcc的动态库的路径。
当我们运行和加载的时候,就和gcc没有关系了,所以我们要告诉操作系统或者加载器 这个动态库的路径!
如果此时新来一个进程,也需要用相同的动态库,操作系统只需要将物理内存中动态库数据经过页表映射到对应进程的地址空间 中的共享区的位置,这样代码区的代码遇到动态库中的函数直接去共享区去找。
那为什么静态库不用呢?
静态链接的一旦编译好,就和库没有关系了,因为已经把库中代码拷贝到自己写的C/C++代码中了.即已经在可执行程序中了.
这个是库加载时搜索的路径.我们只需要把我们的库的路径导入到这个环境变量里面即可.
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:你的库文件路径
导入完成后,此时我们便可以成功运行了
但这个方法有一个缺点,就是每次退出后,我们导入的这些环境变量就会清空,下次还需要重新导入,比较麻烦。
我们既不想在系统中安装我们的库,又想永久保存,那该怎么办呢?
我们只需要在/etc/ld.so.conf.d/路径下 随便创建一个文件.然后vim打开文件,输入你动态库文件路径,然后退出保存即可.
sudo vim /etc/ld.so.conf.d/mylib.conf
然后输入你的库文件路径即可,然后退出,执行重新加载指令
sudo ldconfig
这样也可以照样正常运行了.
到这里动静态库就讲完了,感谢您的阅读哦~