静态库与动态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

生成 发布动静态库

那么我们如何来设计一个静态库呢?我们先写一段最简单的跨文件函数调用的C语言程序

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_02

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_03

/*main函数*/
#include "mymath.h"

int main()
{
int from = 1;
int to = 10;
int result = addToVal(from,to);
printf("result:%d\n",result);
return 0;
}

/*mymath.c*/
#include "mymath.h"

int addToVal(int from,int to)
{
assert(from<=to);
int result = 0;
for(from;from<=to;++from)
{
result+=from;
}
return result;
}

/*mymath.h*/
#pragma once
#include <stdio.h>
#include <assert.h>

extern int addToVal(int from,int to);

/*myprint.c*/
#include "myprint.h"

void Print(const char* msg)
{
printf("%s: %lld\n",msg,(long long)time(NULL));
}

/*myprint.h*/
#pragma once
#include <stdio.h>
#include <time.h>

extern void Print(const char* msg);

我们以前都是这样写的,现在我们要形成静态库,该怎么形成呢?

形成发布静态库

  • 使用gcc将源文件变成 .o文件并打包成库(关于如何gcc的相关使用大家可看我这篇文章)

链接:不就是把所有的.o链接形成一个可执行程序!如果我把我的所有的.o文件给别人,别人能链接使用吗?

答案当然是可以,因此静态链接的本质就是把.o文件合起来形成一个可执行程序,形成静态库就是把多个.o打包起来形成一个库。那么打包成一个静态库我们使用的命令式ar(ar是gnu归档工具archive files)用的选项是rc(c -- create, r--replace) 后面紧跟库的名字(lib开头,静态库.a结尾)

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_04

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_05

至此我们就成功的生成了一个静态库,那么静态库的本质是:我们曾经的源文件翻译成.o文件然后打包起来的操作。

  • 发布静态库

当我们把静态库写好之后,如何让别人使用呢?我们在考虑这个问题时我们站在使用者的角度来思考一下,让你在用库的时候,需要什么东西呢?我们都知道需要的是库文件和头文件。因此我们要想发布静态库,我们需要把库文件和头文件给对方。

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_06

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_07

形成发布动态库

形成动态库和形成静态库的原理基本是一样的,他们的区别是当形成.o文件时需要带-fPIC(position independent code)这一步是需要产生地址无关码。在我们刚刚所写的.o必须存在到进程的地址空间内才能使用。而如果使用fPIC是与地址无关码是把.o加在到内存的任意位置,进程都可以访问使用(采用的是相对地址)。

  • 使用gcc行程.o文件,并且带-shared选项 表示生成共享库格式

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_08

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_09

  • 动态库的交付(发布)

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_10

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_11


一个makefile同时生成动静态库

.PHONY:all
all: libmymath.so libmymath.a

libmymath.so:mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o:mymath.c
gcc -fPIC -c mymath.c -o mymath.o
myprint.o:myprint.c
gcc -fPIC -c myprint.c -o myprint.o

libmymath.a:mymath_s.o myprint_s.o
ar -rc libmymath.a mymath.o myprint_s.o
mymath_s.o:mymath.c
gcc -c mymath.c -o mymath_s.o
myprint_s.o:myprint.c
gcc -c myprint.c -o myprint_s.o

.PHONY:lib
lib:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include

mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include

.PHONY:clean
clean:
rm -rf *.o *.a *.so lib*

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_12

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_13

我们再发布之后查看我们的动静态库

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_14


至此我们动静态库就形成了。

如何使用动静态库

当我们形成动静态库之后我们要站在使用库的人的角度来思考一下如何使用我们所形成的动静态库

使用静态库

首先我们来看看如何使用静态库。

我们首先按照正常的写一个简单的C语言文件,注意我们不在当前路径下,我们在创建一个Test文件夹,让测试的代码在Test的文件夹下,保证test.c测试文件当前路径下没有这两个库。

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_15

#include "mymath.h"
#include "myprint.h"

int main()
{
int from = 1;
int to = 10;
int result = addToVal(from,to);
printf("result:%d\n",result);
Print("hello world\n");
return 0;
}

此时当我们编译这个代码时肯定会报错,因为在当前路径下没有这个头文件,这是理所当然的。

我们之前学习过头文件的搜索路径: "" <>

  1. 在当前路径下查找头文件
  2. 在系统路径下查找头文件

因此既不在当前路径下也不在系统路径下,当然找不到我们刚刚写的库。那怎么办呢?

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_16

正确做法:

1.将系统头文件和库文件,拷贝到系统路径下即可。

sudo cp lib-static/include/* /usr/include/
sudo cp lib-static/lib/* /lib64/

我们再来编译,发现还是报错了.......但是这次报错信息明显已经不是找不到头文件了因此这个错误已经不是头文件没有了,这是一种链接错误。这是因为这是我们使用了第三方库,我们需要在gcc时带-l+库名

库名:去掉前缀(lib),去掉后缀(.*)的名字 例如:libmymath.a 的库名是mymath

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_17

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_18

这时候我们发现就可以了。gcc -l(指明我要链接的第三库的名称)

但是现阶段我们不推荐这个做法,污染系统的头文件集和库,因此我们赶紧把刚才加载到头文件集和库文件的内容删除

sudo rm -f /usr/include/mymath.h
sudo rm -f /usr/include/myprint.h
sudo rm -f /lib64/libmymath.a

删除后我们再编译又回到了刚才的错误,那么我们介绍第二个方法

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_19


2.gcc 指定头文件搜索路径

gcc -I(大i)选项带上头文件所在位置

-L 选项带上所在库的位置

-l 选择带上连接库的名字

gcc test.c -o mytest -I ./lib-static/include/ -L ./lib-static/lib/ -lmymath


[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_20

使用动态库

1.第一种方法和静态库方法一样 拷贝头文件和库文件到系统路径下

2.我们先把动态库拷到当前路径下,然后使用我们使用静态库的第二种方法

cp ../lib-dyl/ . -rf

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_21

gcc test.c -o mytest -I lib-dyl/include/ -L lib-dyl/lib/ -lmymath

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_22

但是当我们成功gcc后以为可以运行了,但是发现还是报错了

我们使用ldd来查看一下程序依赖的库

ldd mytest

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_23

我们发现依赖的动态库找不到呢(not found)那我们刚才不是都已经gcc手动连接了吗?这是因为-I -L -l这是gcc的选项,本质是告诉gcc编译器的,但是形成可执行程序后和gcc就没有关系了。而最后我们进程使用的时候没人告诉我们进程,因此动态库在运行起来时进程找不到这个库了。

运行动态库

动态库的运行搜索路径

./mytest: error while loading shared libraries: libmymath.so
: cannot open shared object file: No such file or directory

那么静态库的时候没有这个问题呢?因此静态在形成可执行程序后已经把需要的代码拷贝进我的代码了,因此在运行时不依赖任何库,因此不需要在运行时查找了。

那么为什么动态库会有这个问题?程序和动态库是分开加载的

那么如何找到动态库呢?

  • 1. 将库拷贝进系统库文件下 拷贝.so文件到系统共享库路径下, 一般指/usr/lib

这个方法比较好理解,但是不推荐,因为会污染系统库

  • 2. 通过导入环境变量的方式 更改 LD_LIBRARY_PATH

因为程序在运行的时候,会在环境变量中查找自己需要的动态库路径 -- LD_LIBRARY_PATH

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Lxy/code/linux-cod  导入环境变量
echo $LD_LIBRARY_PATH -- 查看内容

此时我们再ldd mytest查看发现动态库已经找到了

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_24

我们再运行便可以成功

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_25

但是如果我们关闭xshell再登录进来我们便又没有了

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_26

因此第二种方式只是在当前xshell内有用。

  • 3. 通过系统配置文件 ldconfifig 配置/etc/ld.so.conf.d/,ldconfifig更新

这条命令可以查看我们系统里面,如果我们自定义了动态库,那么此时系统在扫描路径时,不仅在库内扫描,还在当前下扫描。

ls /etc/ld.so.conf.d/

因此我们自己

sudo touch /etc/ld.so.conf.d/libmymath.conf

在这个路径下创建一个文件,把我们动态库的路径写进这个文件中

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_27

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_28

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_29


我们再执行ldconfig让etc文件生效

sudo ldconfig /etc/ld.so.conf.d/libmymath.conf

[ Linux ] 动静态库 手把手教你写一个自己的库_静态库_30

当我们再次关掉xshell时,我们依然可以运行

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_31

  • 4. 其他方式

在系统库文件下创建软连接

sudo ln -s 
/home/Lxy/code//linux-code/practice/11-14/test/lib-dyl/lib/libmymath.so
/lib64/libmymath.so

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_32

此时我们来看ldd mymath,已经连接到库了,我们再运行发现也成功了

[ Linux ] 动静态库 手把手教你写一个自己的库_自定义库_33

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_34


为什么动态库运行时有找库的步骤

在程序地址空间中,我们谈到过堆栈之间的区域有一个共享区。而我们所写的库会被映射到共享区。因此在自己的地址空间中可以执行所有的代码和库的是因为库的代码被映射到自己的共享区。

因此在物理内存中库只有一份。因此动态库为什么叫shared libs 因此在运行期间是被共享的

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_35

[ Linux ] 动静态库 手把手教你写一个自己的库_动态库_36

因此当我们进程在运行的时候,如果要动态加载他所需要的的库,前提条件是先找到这个库在哪里。而静态库根本无需考虑这个问题。

(本篇完)