🐼故事背景
假设今天你有一位舍友。你需要帮助他完成老师的作业。而他写的代码依赖两个文件(mymath.h,mystdio.h)。但是这两个文件的功能他不会写,他只会调用。他的调用代码:
#include"mystdio.h"
#include"mymath.h"
#include<string.h>
#include<unistd.h>
int main()
{
MYFILE* fp = myfopen("log.txt","w");
if(!fp)
{
perror("myfp");
return 1;
}
int cnt = 20;
const char* s = "hello myfile";
while(cnt--)
{
myfwrite(s,strlen(s),fp);
if(cnt == 5)
{
myfflush(fp);
}
}
myfclose(fp);
printf("10+20 = %d\n",Add(10,20));
return 0;
}
但是你会这两个文件具体功能的实现啊。出于热心肠的你现在要帮助他。你会怎么帮助他呢?
🐼静态库制作
1️⃣直接把源文件给他
处于热心肠的你。将你写好的代码mystd.c, mymath.c, mystdio.h, mymath.h这四个文件都给了你舍友。你的舍友通过编译链接完美应付了考试。你帮他顺利过关了。
2️⃣给他.o文件
我们知道。形成一个可执行程序最关键的步骤就是先把源文件编译成.o文件。再把这些.o文件通过链接器链接成.exe文件。如图:
你出于安全考虑,这次不给他源文件了。而是直接将链接好的.o文件交给他。并且给了他说明书(.h)文件
gcc -c *.c
他再将他的test.c编译形成.o文件。和你给的.o大家再一链接又编译形成了可执行程序。这次又顺利过关了!
gcc -c test.c
#-rw-rw-r-- 1 lsg lsg 59 Aug 3 18:09 mymath.c
#-rw-rw-r-- 1 lsg lsg 34 Aug 3 18:09 mymath.h
#-rw-rw-r-- 1 lsg lsg 1232 Aug 3 18:17 mymath.o
#-rw-rw-r-- 1 lsg lsg 2098 Aug 3 18:09 mystdio.c
#-rw-rw-r-- 1 lsg lsg 560 Aug 3 18:09 mystdio.h
#-rw-rw-r-- 1 lsg lsg 3336 Aug 3 18:17 mystdio.o
#-rw-rw-r-- 1 lsg lsg 463 Aug 3 17:58 test.c
#-rw-rw-r-- 1 lsg lsg 2200 Aug 3 18:15 test.o
gcc -o myexe *.o
3️⃣将.o文件打包形成静态库
出于安全考虑。连.o都不想给他了。于是你把你编译好.o打包形成一个静态库。交给了你的舍友并给了他说明书。
为什么能这么做呢?因为你心里清楚。静态库文件的本质就是将.obj进行打包。他的文件最后也要变成.o的。所有.o的文件链接就成了可执行程序。
你:
# 编译将所有.c形成.o
gcc -c *.c
# 将所有.o 文件打包成静态库
ar -rc libmyc.a *.o
// 将形成好的libmyc.a交给你的舍友并且把说明书.h文件也给他。
你的舍友:
#直接编译链接
gcc -o myexe test.c # error
发现报错了!为什么呢?根据报错信息。编译器不认识我给他提供的函数。于是他上网查阅。
发现需要带-l 标明要连接哪个库。因为库多了
为什么要带-l啊。为什么链接C语言不需要带-lc
因为gcc就是编译C语言的。默认就要认识。
而我们如果使用任何第三方库。至少要使用-l表明库名称,指明你要链接谁
# 表明要连接mylibc库
gcc -o myexe test.c -lmyc
# 注意 lib 和.a不需要带
但是发现还是不行根据报错原因。原来是库路径找不到(编译器不认识)。
通过-L指定你要链接库的位置(搜索目录)
gcc -o myexe test.c -L. -lmyc
链接成功!
但是你的舍友想把#include"mystdio.h" #include"mymath.h"换成#include <mystdio.h>
#include <mymath.h> 可以吗?
通过-I指定一个搜索路径。表明头文件搜索路径。这样编译器默认从指定的路径下查找依赖头文件
gcc -o myexe test.c -L. -lmyc -I.
通过上述做法。你的舍友就成功的使用了你给他提供的静态库!
✅但是当你舍友查看库所依赖的文件时。发现并没有查到libmyc.a,这是为什么??
ldd myexe
linux-vdso.so.1 (0x00007ffc62cb8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0a80d2d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0a80f64000)
因为链接的是静态库。静态库本质是.o文件的集合。一旦链接,形成.exe,就不再依赖静态库了。因为静态库早已合并自已的代码到可执行程序中了
✅现在还有一个问题。就是为什么我们链接其他库时(比如pthread库)。并不需要带-L -I 这样的选项。
其实当库没有在系统默认的路径下时。gcc/g++编译器找库。默认会在系统默认路径下查找,而所谓库的安装。就是把库文件拷贝到系统默认路径下。
这里以ubuntu为例。我的库文件系统默认路径是/lib/x86_64-linux-gnu/ (怎么找的,ldd 查看/usr/bin/ls依赖的系统库目录即可)
这是不是意味着。只要我们把库文件拷贝到/lib/x86_64-linux-gnu/。就不用带-L选项。编译器能根据系统指定路径来找到我们的库目录。
并且只要我们把头文件拷贝到/usr/include/ 。就不用带-I选项。编译器能根据系统指定路径来找到我们的源文件依赖的头文件目录。
# 拷贝库文件到系统指定目录下 --->省去了 -L
sudo cp *.a /lib/x86_64-linux-gnu/
# 拷贝头文件到系统执行目录下 ---> 省去了 -I
sudo cp *.h /usr/include/
# 直接连接我们的库即可 -l必须带!!!(因为是第三方库)
gcc -o myexe test.c -lmyc
所以库安装的本质就是将头文件和库文件根据特定目录结构组织好拷贝到系统指定目录下,编译器默认能够找到的目录下!
☑️最佳实践
如果我们想批量化的向别人提供我们写好的库,并以库文件,头文件目录形式打包给给人使用。可以借助Makefile:
开发者:
# 将所有.o打包形成 .a
libmyc.a: mystdio.o mymath.o
ar -rc $@ $^
# 编译并将.c->.o
%.o:%.c
gcc -c $<
.PHONY:clean
clean:
rm -rf *.a *.o output *.tgz
# 发布
.PHONY:output
output:
mkdir -p output
mkdir -p output/lib/
mkdir -p output/include/
cp *.h output/include
cp *.a output/lib/
tar -czf mylib.tgz output
使用者:
tar -xzf mylib.tgz
# 最终将目录树长这样
#tree output
#output
#├── include
#│ ├── mymath.h
#│ └── mystdio.h
#└── lib
# └── libmyc.a
gcc -o myexe test.c -I./output/include/ -L./output/lib -lmyc
如果嫌麻烦。开发者也可以在安装时。自带一个脚本帮使用者把库文件和头文件。拷贝到系统指定目录下。这样就不需要使用者指定头文件位置和库文件位置了
🐼动态库制作
跟形成静态库原理类似。
第一步。将源文件.c编译形成.o文件。注意。在编译形成.o文件时。要带上-fPIC(与位置无关码)
# .c -> .o
gcc -fPIC -c *.c
将.o打包形成动态库
gcc -o libmyc.so *.o -shared
编译并链接动态库
gcc -o myexe test.c -lmyc -L. -I.
运行程序。./myexe
结果报错了!why???
#./myexe
#./myexe: error while loading shared libraries: libmyc.so: cannot open shared object file: #No such file or directory
#lsg@hcss-ecs-0228:~/code/code/25_8_3/person$ ldd myexe
# linux-vdso.so.1 (0x00007ffd9d53f000)
# libmyc.so => not found
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbb1946d000)
# /lib64/ld-linux-x86-64.so.2 (0x00007fbb196a4000)
我们不是已经告诉了系统。库在哪个路径下吗。为什么还是会报错呢? 并且在链接这个库时。系统找不到。这是为什么?
因为我们只告诉了编译器。我们的动态库在哪。而动态库是运行时才会加载到内存中。而此时已经和编译器无关了。所以结论就是在加载可执行程序的时候,也要找到所依赖的库。换句话说。为了运行时,系统(加载器)也能找到动态库,我们需要将库显示拷贝到系统指定目录下。当然,这只是方法之一。只要保证系统(加载器)在加载动态库时也能找到动态库即可。
sudo cp libmyc.so /lib/x86_64-linux-gnu/
#ldd myexe
# linux-vdso.so.1 (0x00007ffc7834c000)
# libmyc.so => /lib/x86_64-linux-gnu/libmyc.so (0x00007f2c3c987000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2c3c75e000)
# /lib64/ld-linux-x86-64.so.2 (0x00007f2c3c99a000)
我们也可以通过软链接的方式关联将库文件的安装目录进行关联
我们将动态库也以目录形式打包发布出去:
如果我们自已使用(软连接可以根据你的绝对路径修改,这里小编就不改了)
libmyc.so:mystdio.o mymath.o
gcc -o $@ $^ -shared
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -rf *.o *.so
sudo unlink /lib/x86_64-linux-gnu/libmyc.so
sudo unlink /usr/include/mymath.h
sudo unlink /usr/include/mystdio.h
.PHONY:self
self:
sudo ln -s /home/lsg/code/code/25_8_3/mylib/libmyc.so /lib/x86_64-linux-gnu/libmyc.so
sudo ln -s /home/lsg/code/code/25_8_3/mylib/mymath.h /usr/include/mymath.h
sudo ln -s /home/lsg/code/code/25_8_3/mylib/mystdio.h /usr/include/mystdio.h
// 自已写的库完全可以拷贝到系统指定目录下。就不用软连接了(软连接只能在你指定的路径下使用你的库)
☑️最佳实践
如果我们自已想用自已写好的库,直接拷贝到系统指定目录下!
libmyc.so:mystdio.o mymath.o
gcc -o $@ $^ -shared
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -rf *.o *.so
sudo rm /lib/x86_64-linux-gnu/libmyc.so
sudo rm /usr/include/mymath.h mystdio.h
.PHONY:copy
copy:
sudo cp mymath.h mystdio.h /usr/include/
sudo cp libmyc.so /lib/x86_64-linux-gnu/
发布给别人使用
libmyc.so:mystdio.o mymath.o
gcc -o $@ $^ -shared
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -rf output *.o *.so *.tgz
.PHONY:output
output:
mkdir -p output
mkdir -p output/lib/
mkdir -p output/include/
cp *.h output/include/
cp *.so output/lib/
tar -czf mylib.tgz output
当然。运行时找到动态库的原理还有几种。比如配置 LD_LIBRARY_PATH环境变量。
更改系统配置文件。将动态库,查找路径,使其全局有效。
还需要注意的是:
✅动态库和静态库同时存在的时候,gcc/g++优先使用动态库。默认进行动态链接!
✅一个可执行程序。可能依赖多个库,但是如果我们只提供静态库,即使动态链接。gcc也没办法,只能对只提供的静态的库,进行静态链接!
✅如果我们显示带上-static选项。那么我们就一定要提供静态库了,否则,会报错!