静态库与动态库的概念
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
设计静态库
编写一个简单库,实现加减乘除的算法
cpp
// mymath.h文件
#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.c文件
#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;
}
return x / y;
}
当需要将自己实现的算法给别人使用的方法:
- 将源文件直接给别人(暴露源文件内容)、
- 把源文件的代码打包成库(不会暴露源文件内容),给别人提供的代码是 = 库 + .h
【注意】必须提供库(也就是头文件),库相当于说明书
形成静态库
生成静态库的方式就是将.c文件形成.o文件,再将.o文件打包形成.a静态库,即可使用静态库。

cpp
// makefile文件
lib=libmymath.a
$(lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
.PHONY:clean
clean:
rm -f *.o *.a
指令:ar -rc xxx.a xxx.o xxx.o
【功能】ar是生产静态库的一个命令,-rc代表replace and creat,意思是将.o文件放在.a的二进制文件里面

此时形成mymath.o文件,然后将mymath.o文件打包形成libmymath.a文件
发布静态库
在makefile文件中编辑:
- .PHONY:output
- output:
- mkdir -p lib/include
- mkdir -p lib/mymathlib
- cp *.h lib/include
- cp *.a lib/mymathlib
创建一个目录,将.h文件和.a文件放在lib文件中,如果别人需要使用该库,直接将lib文件夹给别人即可。
bash
lib=libmymath.a
$(lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
.PHONY:clean
clean:
rm -rf *.o *.a lib
.PHONY:output
output:
mkdir -p lib/include
mkdir -p lib/mymathlib
cp *.h lib/include
cp *.a lib/mymathlib

使用静态库

cpp
#include "mymath.h"
int main()
{
printf("1+2=%d\n", add(1,2));
return 0;
}

【错误】头文件找不到
使用#include"mymath.h",系统会默认
- 在/usr/include/系统路径下寻找。
- 当前目录寻找。
显然,我们目前的mymath.h既不在当前目录,也不在系统目录中。
找到头文件的方法:
指令: gcc main.c -I [path]
【功能】gcc带 -I 选项,意思让include除了在当前目录下和系统目录下寻找,还在指定目录下寻找,-I代表include的意思

【错误】找不到add这个函数的实现,这种错误发生在链接时报错,因为.o文件是可以出来的。

找不到函数的链接,是找不到静态库.a文件,系统会默认在:
- 系统目录/lib64/libc.so路径下找静态库文件
- 系统目录/lib64/libc.a路径下找静态库文件
- 当前目录下找静态库文件
找到静态库文件的方法:
指令:gcc main.c -I [path] -L [path]
【功能】-L在指定路径下找静态库文件,-L代表lib的意思

【错误】./lib/mymathlib/目录下不仅仅会存在一个静态库文件,需要显式的说明需要链接哪一个静态库。而./lib/include没有明确说明需要哪一个头文件,是因为在main.c文件中已经显示说明需要使用哪一个头文件。
指令:gcc main.c -I [path] -L [path] -l[静态库]
【功能】-l 代表link的意思,会找指定的静态库

【注意】静态库的真实名称式去掉前缀lib和后缀.a或者.so的名称


总结通过指令的方法使用静态库
- 指令:gcc main.c -I [path] -L [path] -l[静态库 舍去lib前缀和.a/.so后缀]
【注意】使用第三方库,必须使用-l带第三方库
特殊情况分析
当在main.c文件编写代码时,出现这种情况:
cpp
#include "mymath.h"
int main()
{
printf("10/0=%d\n", div(10,0));
return 0;
}

【错误分析】此时计算结果为-1,无法清楚的知道是否在调用函数出错,还是本来就是该计算结果。
cpp
1 #include"mymath.h"
2
3 int myerrno = 0;
4
5 int add(int x, int y)
6 {
7 return x + y;
8 }
9
10 int sub(int x, int y)
11 {
12 return x - y;
13 }
14
15 int mul(int x, int y)
16 {
17 return x * y;
18 }
19
20 int div(int x, int y)
21 {
22 if(y == 0)
23 {
24 myerrno = 1;
25 return -1;
26 }
27 return x / y;
28 }
cpp
#include "mymath.h"
int main()
{
int n = div(10, 0);
printf("10/0=%d, myerrno=%d\n", n, myerrno);
return 0;
}

可以通过设置errno的方式来解决此问题,当errno为1说明结果是错误的,当errno为0说明结果是正确的。
【结论】
- 第三方库,在使用的时候,必须使用gcc -l
- errno的本质就是全局变量。
- gcc 默认是动态链接,但是如果在找的地方只有静态库,gcc 会选择使用静态链接,-static选项是一种建议选项。系统中动态库和静态库均提供时,默认会选择动态链接,带选项-static会使用静态链接,当系统中只存在动态库,不管有没有static选项都会动态链接。当只存在静态库时,只会静态链接。
- 如果系统中需要链接多个库,那么gcc可以链接多个库
将头文件和库文件放在系统文件下
指令:sudo cp 当前目录下的头文件 /usr/include
【功能】将头文件保存在系统头文件目录

指令:sudo cp 当前目录下的库文件 /usr/lib64
【功能】将头文件保存在系统库文件目录


此时,只需要链接需要哪个库文件即可。
【说明】将头文件或者库文件拷贝到系统路径下,本质是在进行库的安装。
使用软连接的方式
指令:ln -s [目录名称] [软连接名称]
【说明】将需要使用的头文件或者库文件建立软连接。

cpp
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ sudo ln -s /home/dabai/git/linux/1218/test/lib/include/ /usr/include/myinc
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls /usr/include/myinc
mymath.h
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls -l /usr/include/myinc
lrwxrwxrwx 1 root root 44 Dec 19 10:00 /usr/include/myinc -> /home/dabai/git/linux/1218/test/lib/include/
cpp
#include "myinc/mymath.h"
int main()
{
int n = div(10, 0);
printf("10/0=%d, myerrno=%d\n", n, myerrno);
return 0;
}
使用绝对路径的方式,在系统路径下设置软链接,通过软链接找mymath.h头文件。

cpp
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ sudo ln -s /home/dabai/git/linux/1218/test/lib/mymathlib/ /lib64/libmymath.a
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls -l /lib64/libmymath.a
lrwxrwxrwx 1 root root 46 Dec 19 10:08 /lib64/libmymath.a -> /home/dabai/git/linux/1218/test/lib/mymathlib/
使用绝对路径的方式,在系统路径下设置软链接,通过软链接找库文件。
设计动态库
设置四个文件,形成动态库

myprint.*文件
cpp
// myprintf.h
#pragma once
#include<stdio.h>
void Print();
// myprintf.c
#include "myprintf.h"
void Print()
{
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
}
mylog.*文件
cpp
// mylog.h文件
#pragma once
#include<stdio.h>
void Log(const char*);
// mylog.c
#include "mylog.h"
void Log(const char* info)
{
printf("Warning :%s\n", info);
}
形成动态库
通过指令的方式形成动态库
指令:
- gcc -fPIC -c [filename]
- gcc -shared -o lib[filename].so
【功能】
- fPIC:产生位置无关码
- -shared:形成共享库格式
首先将.c文件形成.0文件

然后将.o文件打包,形成动态库

使用makefile的方式形成动态库
makefile文件
cpp
dy-lib=libmymethod.so
static-lib=libmymath.a
.PHONY:all
all: $(dy-lib) $(static-lib)
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
$(dy-lib):mylog.o myprintf.o
gcc -shared -o $@ $^
mylog.o:mylog.c
gcc -fPIC -c $^
myprintf.o:myprintf.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf *.o *.a *.so lib
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib


使用动态库

将mylib目录放在test目录下面,让test进行使用。
编辑main.c文件
cpp
#include "mylog.h"
#include "myprintf.h"
int main()
{
Print();
Log("hello log function");
return 0;
}
指令:gcc main.c -I mylib/include/ -L mylib/lib/
【功能】将动态库链接

指令:gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod -lmymath
【功能】将动态库和静态库都链接,这里如果需要添加库,只需要在后面加 -l...
cpp
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ./a.out
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory
链接动态库时会出现上述问题,问题出现的原因是,编译器找到动态库,但是加载器没有找到动态库:
指令:ldd [可执行文件]
【功能】查看动态链接

方法一:在系统共享库处建立软连接
cpp
sudo ln -s /home/dabai/git/linux/1218/test/mylib/lib/libmymethod.so /lib64/libmymethod.so
如果出现ldd发现没有链接成功,则输入指令:
# 直接指定软链接所在路径(/lib64已在默认搜索路径,但需缓存,此方式绕开缓存)
export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH
此时,即可使用动态库:

方法二:拷贝到系统默认路径即可
该方法与静态库方法类似
方法三:添加到系统环境变量
# 直接追加你的库路径(推荐,不覆盖原有环境变量)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dabai/git/linux/1218/test/mylib/lib

将自己的库所在的路径,添加到系统的环境变量中。
方法四:在/etc/ld.so.conf.d/下创建自定义配置文件,指定库路径
sudo echo "/home/dabai/git/linux/1218/test/mylib/lib" > /etc/ld.so.conf.d/mymethod.conf
【总结】动态库在进程运行的时候,是要被加载的,而静态库不需要。常见的动态库被所有的可执行程序(动态链接的),都要使用动态库------共享库,所以,动态库在系统中加载只会,会被所有进程共享。
动态库是怎么被加载的
动态库也是文件,动态库加载到物理内存中,与页表建立映射,保存在进程地址空间中的共享区(栈区与堆区之间的区域)。当建立映射之后,执行的任何代码,都是在进程地址空间中执行的。
而系统在运行中,一定会存在多个动态库,这些动态库会被操作系统管理起来(先描述再组织),也就是说操作系统了解所有库的加载情况。当存在多个进程需要使用一个库的情况时,操作系统会分析物理内存中是否存在该库,如果存在,直接会让这些进程直接建立联系,将库的代码通过页表映射保存在共享库中,也就说在物理内存中只需要保存一份库的代码即可,这个库也被称为动态库/共享库。

++问题:当动态库中设置全局变量时,例如前面的errno,多个进程是如何处理的:++
【答】:库是在堆区和栈区之间的共享区的,当存在对全局变量进行写入的时候,会造成写时拷贝。
关于地址相关的知识
程序没有加载前的地址(程序)
程序在编译好之后,程序内部有地址的概念,是因为编译器也需要考虑操作系统,这些地址已经是虚拟地址,但是为了区分虚拟地址空间的地址,将程序刚编译好的地址称为逻辑地址,逻辑地址是磁盘中程序编译后的地址。
【注意】可程序程序在编译后就存在地址,该地址称为逻辑地址。
cpp
#include<stdio.h>
int a = 10;
int b = 20;
int main()
{
printf("hello world!\n");
int x = 30;
int y = 40;
int z = x + y;
printf("result: %d\n", z);
return 0;
}

指令:objdump -S a.out
【功能】将a.out程序进行反汇编。


【说明】这些指令都是CPU进行执行的。
程序加载后的地址(进程)
将可执行程序加载到内存的时候,每条指令都占据一定的物理地址,而每条指令自己内部又保存着自己的逻辑地址。
当可执行程序编译好之后,会在代码的开头存放一个入口地址,这个入口地址不是物理地址,而是逻辑地址。在CPU中会存在一个寄存器,寄存器将读取入口地址(逻辑地址),然后找到虚拟地址空间的起始地址,然后通过页表映射,找到物理内存中的代码。
在CPU中读取到指令的时候,可以存在数据,也可能存在地址,这个地址是逻辑地址,然后寄存器会在虚拟地址空间中查找该逻辑地址,通过页表映射再找到物理地址,当物理地址不存在时,会进行缺页中断。
【注意】在代码中读取到的地址都是逻辑地址,通过逻辑地址和页表的映射可以找到函数的物理地址。
动态库的地址
将动态库放到物理内存中,通过页表映射会放在进程地址空间中的共享库的位置。而共享库很大,动态库被加载到固定地址空间中的位置是不可能的。动态库被加载的时候,库可以在虚拟内存中,任意位置加载。
所以动态库会在自己内部函数不采用绝对编址,而是采用每个函数在库中的偏移量即可。
- -fPIC:产生位置无关码
该选项的作用是直接用偏移量进行对库中的函数进行编址。
【问题】静态库为什么不谈加载,不谈与位置无关?这是因为静态库会直接将代码加载到可执行程序中。静态库的代码会放在进程地址空间中的代码区中,以固定地址的方式。