目录
静态库
概念:静态库是指 程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库,后缀为.a
制作静态库
将下列代码制作成静态库,并且使用
mymath.h
#include<stdio.h>
int Add(int x, int y);
int Sub(int x,int y);
int Mul(int x,int y);
int Div(int x,int y);
mymath.c#include"mymath.h"
int Add(int x,int y)
{
return x+y;
}
int Sub(int x,int y)
{
return x-y;
}
int Mul(int x,int y)
{
return x*y;
}
int Div(int x,int y)
{
return x/y;
}
首先制作静态库需要我们提供编译成了.o文件的源文件和头文件。可以有多个.o文件,库函数的本质就是将多个.o文件形成一个文件方便我们使用。头文件给我提供库函数内函数的声明,所以也是必不可少的。
我们利用Makefile来帮我们形成静态库
Makefile文件:
static-lib=libmymath.a
$(static-lib):mymath.o //若有多个.o文件在后面加上即可
ar -rc @ ^ //ar是用来生成静态库的指令,rc代表replace and create(清空并创建)
mymath.o:mymath.c
gcc -c $^
.PHNOY:clean
clean:
rm -rf *.o *.a mylib
.PHONY:output //用于将我们的静态库放在一个文件内
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp ./*.h mylib/include
cp ./*.a mylib/lib
执行之后我们看到我们的头文件个静态库以及存放在了mylib文件内了,我们的静态库也就生成了

使用静态库
我们以下面的实验代码为例:调用静态库中的Add函数
cpp
#include "mymath.h"
int main()
{
printf("2+2= %d\n",Add(2,2));
return 0;
}
同时将我们的mylib文件copy到我们的实验文件内:

此时我们可以按照之前的编译方式尝试编译一下我们的源文件,会发现我们的编译器无法找到我们的头文件,导致编译失败。

直接编译
正确的编译方法如下:
gcc main.c -I mylib/include -L mylib/lib -lmymath

解释如下:
在我们编译静态库文件的时候,由于编译器回去他的默认路径下面寻找头文件以及相应的静态库,当使用我们指定的静态库时,需要用 -I给编译器指出我们头文件的所在的绝对或者相对路径,需要用 -L 指出文件静态库所在的绝对或者相对路径,并且要用 -l 指出我们有用的静态库文件名。注意:静态库文件名是指去掉前缀lib和后缀之后的名称
除了用上述方法我们还可以将静态库直接放入我们编译器默认的文件内,和软链接两种方法。
放入默认路径:
将头文件放入/usr/include文件内

静态库文件放入/usr/lib64文件内

直接使用:放入默认路径后不需要指定路径,但是我们自己写的库仍然需要指定静态库

创建软连接
在/usr/include文件内创建我们头文件路径的软连接

在/usr/lib64文件内常见我们静态库的软连接

直接使用: 
注意:由于我们的软链接存放的是指向文件的路径,所以我们的main.c中的头文件要改成"myinc.mymath.h",需要指定在我们myinc的路径下的头文件
动态库
制作动态库
利用下面多个源码来制作动态库
myprint.h
#pragma once
#include<stdio.h>
void Printf();
myprint.c#include"myprint.h"
void Printf()
{
printf("hello wrold!\n");
}
mylog.h#pragma once
#include<stdio.h>
void Log(const char*str);
mylog.c
#include"mylog.h"
void Log(const char*str)
{
printf("hello %s wrold!\n",str);
}
制作动态库和静态库的方法是类似的,只是有一些细节需要我们注意,我们同样用Makefile来制作我们的动态库。其中保留我们静态库的方法
static-lib=libmymath.a.PHNOY:all //Makefile一次形成多个文件
all: (static-lib) (dy-lib)
$(static-lib):mymath.o //静态库制作方法
ar -rc @ ^
mymath.o:mymath.c
gcc -c $^
$(dy-lib):myprint.o mylog.o //动态库的方法
gcc -shared -o @ ^
myprint.o:myprint.c
gcc -fPIC -c $^
mylog.o:mylog.c
gcc -fPIC -c $^
.PHNOY:clean
clean:
rm -rf *.o *.a mylib
.PHONY:output //用于将生成的动静态库放在同一文件内
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp ./*.h mylib/include
cp ./*.a mylib/lib
cp ./*.so mylib/lib
制作动态库我们可以直接使用gcc编译器来制作, gcc -shared -o .... 我们利用gcc中的**-shared**命令就可以设置动态库,但是我们动态库所依赖的.o文件的编译必须使用在编译时加上 -**fPIC,**产生位置无关码(position independent code) ,下面我们来解释下为什么要加上这个fPIC。
为什么需要fPIC(动态库的加载)
首先我们要知道,我们的动态库可以被多个文件所链接的,他是不会直接载入到我们的原文中的,而是需要的时候去我们的动态库里去用,我们的动态库也是一个文件,他被利用的时候也是需要放入内存中的,当我们的文件需要去链接他的时候,只需要把他链接到我们执行文件的共享区即可,存放的也只是我们动态库文件的起始地址(为什么后面有解释)

程序加载前
我们知道我们的进程有PCB、进程地址空间、页表、物理空间等,在我们的一个可执行程序编译的时候,就会形成我们所谓的虚拟地址,利用平坦模式进行编译,所以我们编译完成的可执行程序中是存在我们的虚拟地址的。
程序加载后
当我们运行这个可执行程序的时候,就会创建我们PCB等进程内容,同时将我们的代码和数据载入内存中。
当我们编译后的可执行程序放入内存中时,里面存在的内容是包含虚拟地址的(例如函数的调用)所以我们就需要将我们的我们的虚拟地址转换为物理地址,才能让我们的CPU去访问,
我们知道页表可以将我们的虚拟地址和物理地址进行映射。在进程放入数据进入内存的时候,就知道我们的数据和代码是放在物理内存的那块区域的,所以只需要在页表中填写相映信息即可。
动态库的加载
当我们在一个文件中调用我们的库函数的时候,他会将这个库函数变成类似于 call 0x1122 这样的之地址来让我们调用,这个是在编译的时候就以及完成的,所以0x1122 一定是一个虚拟地址,但是将他加载到我们的内存中时,我们的这个虚拟地址是不会改变的,那么在页表中0x1122对应的这个物理地址,我们该怎么保证它一定是我们库函数所在的物理地址呢?将动态库固定在物理内存的某个位置是不现实的。所以我们采用的是相对地址的方法,这里的0x1122 其实是该函数在动态库中的偏移量,我们只需要知道库函数的起始地址就可以找到我们所调用的函数地址了,这也就是为什么我们的进程地址空间中的共享区需要存储我们动态库的起始地址的原因。我们只需要根据我们的动态库的起始地址以及我们在编译时的偏移量就可以找到我们的库函数了。
而我们的fPIC也就是告诉我们的编译器,我们在编译的时候,将这里面的函数全部都设置为偏移量,不要变成绝对地址。
动态库的使用
直接编译
gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod

但是我们运行的结果如下:

这是因为我们在gcc编译的时候我们告诉了编译器我们的动态库的在哪和链接那个动态库,但是当我们编译完成之后, 我们运行程序和我们的编译器就没有关系了,我们运行程序时通过我们Linux的加载器完成,我们没有告诉我们的加载器我们的动态库在哪里,所以我们依然无法编译。
解决方法:
1、拷贝到我们/lib64或者/usr/lib64


2.在我们的/lib64 或者/usr/lib64目录下创建软链接

3.将我们库的路径配置到我们的LD_LIBRARY_PATH中


4.在我们/etc/ld.so.conf.d 建立自己动态库的路径文件,然后ldconfig重新加载配置

