文章目录
前提:
把自己的方法给别人用,有两种方法
- 把自己的源代码给他
- 把自己的方法打包成库(库+.h)
静态库
原理
把.c文件汇编成.o文件后,把.o文件打包成.a文件
再将.a文件和main.c链接起来
静态库链接时会把库代码连接到可执行文件中,从此可执行文件不需要静态库
静态库命名标准是 lib+库名+.a
实现
mymath.h
c
#pragma once
#include <stdio.h>
extern int myerrno;
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.cpp
c
#pragma once
#include "mymath.h"
int myerrno=0;
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)
{
if(y==0)
{
myerrno=1;
return -1;
}
else{
return x/y;
}
}
Makefile
c
static-lib=libmymath.a
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.cpp
g++ -c $^
.PHONY:clean
clean:
rm -rf *.o *.a
.PHONY:output
output:
mkdir -p lib/include
mkdir -p lib/mylib
cp *h lib/include
cp *a lib/mylib
ar是gnu归档工具,rc(replace and create)
main.cpp
c
#include "mymath.h"
int main()
{
extern int myerrno;
int x=div(10,0);
printf("%d\n",sub(1,6));
printf("10/0=%d errno=%d\n",x,myerrno);
return 0;
}
链接:
g++ main.cpp -L. -lmymath
-L后面加库路径 -l加库名称
记得去掉lib和.a编译器会自动补齐

🚩动态库
把.c文件汇编成.o,再把.o打包成.so
将main.c链接动态库所在的路径
相比静态库直接加载到可执行程序中,动态库程序运行时才去链接动态库,多个程序共用动态库,节省内存和硬盘空间
实现
myprint.h
c
#pragma once
#include <stdio.h>
void print();
myprint.cpp
c
#include "myprint.h"
void print()
{
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
}
mylog.h
c
#pragma once
#include <stdio.h>
void log(const char*);
cat mylog.cpp
c
#include "mylog.h"
void log(const char* info)
{
printf("warning!:%s\n",info);
}
Makefile
c
dy-lib=libmymethod.so
$(dy-lib):mylog.o myprint.o
g++ -shared -o $@ $^
mylog.o:mylog.cpp
g++ -fPIC -c $^
myprint.o:myprint.cpp
g++ -fPIC -c $^
.PHONY:clean
clean:
rm -rf *.o *.a lib *.so
.PHONY:output
output:
mkdir -p lib/include
mkdir -p lib/mylib
cp *h lib/include
cp *a lib/mylib
cp *so lib/mylib
mytest.cpp
c
#include "myprint.h"
#include "mylog.h"
int main()
{
log("log function");
print();
return 0;
}
g++ mytest.cpp -o mytest -L. -lmythod
make output然后创建test,我们把lib移进test,

我们在test目录里把main.cpp链接动态库会发生什么?
g++ main.cpp -o test -I./lib/include -L./lib/mylib -lmymath -lmymethod
-I 是寻找找头文件
再执行test文件

出问题了!!
之前是动态库文件和可执行文件在同一目录
现在分开链接,加载器(系统)不知道动态库在哪里
我不是g++ -L告诉你了吗??
那是告诉编译器的
现在是可执行文件,与编译器无关了
🚩解决问题
怎么解决!!!
最常用的也是最简单的方法,为什么我们执行C语言程序,没出问题,因为系统会自动去默认特定目录去寻找动态库
我们只需要把我们自己的库复制到系统目录即可
sudo cp lib/mylib/libmymethod.so /usr/lib/


我们一般都是把库装到系统中
或者动态库软链接到系统目录中
sudo ln -s /home/jib/1010/static-dy-library/libmymethod.so /usr/lib/libmymethod.so
然后刷新下动态库
sudo ldconfig

🚩动态库加载原理
动态库使用是要被加载到内存中的,常见的动态库要被所有可执行程序(动态链接)使用,
所以动态库是要被所有进程共享的
怎么做到的?

先从.exe入手,启动进程,正文代码地址连接到页表中,文件系统将文件从硬盘中加载到物理内存中,分为数据和代码,连接到页表中
其实动态库也相当于.exe文件,也需要加载到物理内存中,但是进程那边,关于动态库的代码会被放入共享库中,正文代码需要动态库就去共享库寻找,再通过页表与物理内存中.so连接==,完成之后返回到正文代码中,
此时,再开一个进程,该进程与其他进程毫不相干,但是依旧采用上述过程,实现了动态库的共享
事实上,系统在运行中,会存在很多动态库,OS会把他们管理起来,先描述,再组织,
系统中,所有动态库的加载情况,操作系统一清二楚
结论:页表建立映射关系之后,我们执行的代码都是在虚拟内存地址中解决
🚩-fPIc
意思为与地址无关码,编译动态库时使用,也是动态库实现的关键
先说一个问题,两个进程的虚拟地址表相同的虚拟地址映射的物理地址是相同的吗
:不一定,对于共享区和只读数据段是相同的,因为他们不会被修改
对于可读写数据就不相同,要确保进程的隔离性
-fPIc 从全局变量中偏移一段地址,简单来说就是,从某个大家都知道的位置(GOT全局偏移表),移动一段地址就能找到动态库
没有-fPIc怎么样?
如果没有-fPIc,我们存到一个进程的虚拟地址表中,等到又来一个进程,因为相同虚拟地址映射物理地址不同,我们的动态库地址可能已经被占用了,
即使不被占用,我们还要再存储一遍,物理内存中又要再复制一遍动态库代码,这就违反了共享库原则

就好比大家都知道40米处有棵树,往后偏移10米存储动态库
🚩关于地址
程序未加载之前的地址
编译器会考虑操作系统,
事实上,在代码编译成功之后,硬盘中就存在虚拟地址(其实叫做逻辑地址)了,
地址以平坦模式存储在硬盘中(之前把地址分段隔开,要寻找地址非常麻烦,平坦模式中段依然存在,但是段和段之间连续,偏移量看作线性地址,当作0-4GB的连续平坦的空间)

程序被加载之后

整个流程分析!!
1,编译器编译代码,形成逻辑地址存在硬盘中
2,进程创建出来,并且分配虚拟地址空间,同时逻辑地址直接当作虚拟地址使用
3,硬盘中的数据加载到物理内存中,同时建立页表,把虚拟地址和物理地址建立映射
4,CPU从EIP/PC中取得虚拟地址,再通过页表翻译成物理地址,然后访问物理地址指令/数据,执行代码
此外,进程启动时,会把入口虚拟地址直接给EIP/PC,CPU执行完该地址之后,EIP会跳转到下一条物理地址