其实库文件里面的内容就是函数的实现方法,向我们包含的头文件其实就是函数的生命,而我们编译链接程序时会自动加载库文件,最终形成可执行程序。其实我们在编译链接时不仅仅会将文件的库文件加载进来,其实头文件也是需要加载进来的,只不过也是自动加载的。
静态库
静态库(.a):程序在编译链接的时候把库的代码链接拷贝到可执行文件中。程序运行的时候将不再需要静态库。
模拟实现静态库
其实库文件的形式都是.c文件编译成的.o目标二进制文件,然后全部打包给我们用户进行使用。如果我们用户的main函数中使用了对应库文件的函数方法,那么我们用户可以将main函数编译成.o文件,再将所有.o文件链接起来形成可执行程序即可实现。
现在我们开始模拟实现库文件,首先就是实现函数生成.o文件,所以此时的指令就是gcc -c test.c -o test.o,其实后面不接-o也是没问题的,会自动生成同名的.o文件,所以此时就十分轻松。
此时拿一个add函数和sub函数举例,实现函数声明定义分离的形式:
"add.h"
#pragma once
#include<stdio.h>
int add(int ,int);
"sub.h"
#pragma once
#include<stdio.h>
int sub(int,int);
"add.c"
#include"add.h"
int add(int x,int y)
{
return x+y;
}
"sub.c"
#include"sub.h"
int sub(int x,int y)
{
return x-y;
}
"main.c"
#include"add.h"
#include"sub.h"
int main()
{
printf("%d+%d=%d\n",3,4,add(3,4));
printf("%d-%d=%d\n",3,4,sub(3,4));
return 0;
}
生成.o文件和.exe的makefile:(#是注释)
test.exe:test.o add.o sub.o
gcc -o $@ $^
%.o:%.c # %类似于通配符,会查找当前目录下的所有后缀为.c的文件,并且会生成相同文件名的.o文件
gcc -c $< # $<相当于一个个的.c文件各自拿过来编译
.PHONY:clean
clean:
rm -f test.exe
而生成静态库的指令就是:ar -rc xxx.a xx.o x.o 其中rc表示(replace and create)
生成库文件的makefile:
libmymath.a:*.o
ar -rc $@ $^ #生成静态库
.PHONY:clean
clean:
rm -f *.a
最后就是将库文件和我们的main.c文件链接生成可执行程序了。
链接生成可执行程序:
gcc main.c -I ./ -l mymath -L ./
-I 指定头文件路径(当前路径的话会默认查找)
-L 指定库路径
-l 指定库名
首先我们要了解一下后面的指令参数,-l和-L,而我们在没有链接我们实现的静态库时,而是gcc编译器会自动的链接C语言的库,所以就不需要这两个指令参数。在此我们要知道我们写的库叫做第三方库,所以在形成可执行程序的时候需要链接我们的第三方库,而且一般还要指明路径。并且我们链接的库名称是需要去掉前缀lib和后缀.a的。
默认搜索库和头文件:
在Linux中我们进行编译C语言文件的时候系统默认会在/usr/include中搜索头文件的,而在库文件都是在/lib64里搜索的。所以我们也可以将我们自己的的头文件和库文件拷贝到对应的路径下,那么在之后编译链接时就会直接默认像路径中寻找对应库文件链接生成可执行程序。
动态库
动态库是一种在运行时被可执行文件引用的库文件。它是独立于可执行文件的外部文件,可以被多个程序共享使用。动态库的文件扩展名一般为.so
(在Windows下为.dll
)。动态库的主要特点是在程序运行时动态加载,可以节省系统内存,同时也方便了库的更新和维护。
动态库模拟实现
其实和静态库的操作是没啥区别的,只不过有些指令的参数不同。首先还是生成.o文件,然后再将.o文件生成动态库。
makefile的实现:
libmymath.so:add.o sub.o
gcc -shared -o $@ $^ # 生成共享库格式
%.o:%.c
gcc -fPIC -c $< # 产生位置无关码,也就是相对编址的方式
.PHONY:output # 发行
output:
mkdir -p mymath/include
mkdir -p mymath/lib
cp -f *.h mymath/include
cp -f *.so mymath/lib
.PHONY:clean
clean:
rm -rf *.o *.so mymath
将发行的文件mymath拷贝到一个新的目录文件下:
链接生成可执行程序:
gcc main.c -l mymath -L mymath/lib -I mymath/include
-l :指定库名称(去掉前缀lib和后缀.so)
-L :库的所在路径
-I :头文件所在路径
运行可执行程序:
但是我们其实还是无法直接进行运行的。
ldd指令查看所需动态库:
因为对于静态库来说静态库会被整个复制到可执行文件中,所以一旦编译成功了之后就和静态库没什么关系了,可执行程序也就可以直接运行,所以运行期间也不需要查找静态库。而对于动态库就不一样了,动态库和可执行程序是分离的,也就是两个不同的文件,所以在运行我们可执行程序的时候就要将我们的程序和动态库都加载到内存中才可以运行起来。
动态库运行时的解决方法
- 直接将我们的第三方动态库安装(拷贝)到系统默认的动态库/lib64目录下。也可以顺带将头文件拷贝到/usr/include下,那么我们在下次gcc编译的时候就只需要带上链接动态库的文件名就行了。
- 将库文件拷贝到与main.c文件在同一个目录路径下(因为动态链接时,运行可执行程序时不仅仅会在/lib64查找库文件,也会默认在当前目录下查找库文件)其实也可以通过创建软硬链接的方式进行:(软硬链接后的名称一定要与所需动态库同名)
- 直接更改系统中关于动态库的配置文件(系统中配置文件的位置在/exc/ld.so.conf.d/中,所以我们需要在该路径下touch一个文件,然后我们vim文件,直接将我们的动态库绝对路径写入文件中即可,最后ldconfig命令刷新一下就可以。而且该配置文件内容始终有效)
- 将我们实现的库拷贝到环境变量LD_LIBRARY_PATH中(因为我们执行可执行程序时,系统除了会在默认路径下帮我们加载库文件外,还会在该环境变量中搜索对应的动静态库。但是只是在内存中写入,所以下次使用就不存在了)
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH : (我们库所在的路径)
我们在链接动态库的程序在运行期间也是需要库文件的,其实本质就是在编译期间会形成符号表,记录函数名与函数在库里的地址映射关系,而最后运行期间加载到内存的时候,如果主函数调用了动态库中的函数时,就会找到函数地址,但是此时需要将库文件也一同运行到内存中,这样才可以找到需要调用的函数。
gcc默认都是链接动态库 :
如果我们同一个库的目录文件下提供了动静态两种库,那么在gcc编译时会默认使用动态库,除非后面带上 -static选项才会使用我们的静态库。
gcc main.c -l mymath -L mymath/lib -I mymath/include //默认动态链接
gcc main.c -l mymath -L mymath/lib -I mymath/include -static //强制静态链接该库
使用外部库
- 安装库。可以通过yum install -y等指令进行安装外部第三方库。
- 将头文件和库文件分别安装到对应的默认加载路径里。如果yum自动安装了的话就不用了
- gcc编译。不要忘记加上 "-l 库名"