作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍下动态库和静态库的概念
我们首先写出下面的一段很简单的c语言代码
1 #include 2 3 int main()4 {5 printf("hello world\n"); 6 return 0;7 }
这段代码我们在一开始学习c语言的时候就会写了 它的运行结果我们当然也很清楚 会向显示器打印 hello world
再回到上面的那段代码中 我们有没有想过这样一个问题
为什么我们调用 printf函数 就能够向屏幕打印信息呢?
通过前面基础IO和操作系统部分的学习
基础IO
操作系统
我们知道c语言其实是用户层的函数
它的下面有系统调用接口 系统调用接口经过封装之后变成为了我们的printf函数
而系统调用接口接受这个指令之后操作系统便指挥显示器的驱动向屏幕打印函数内的信息
也就是说我们在语言层面所直接使用的函数其实全部是别人封装好的库(lib)
而我们上面的那段代码之所以可以运行就是因为gcc编译器在生成可执行文件的时候将c语言标准库连接了进来
我们怎么查看自己使用了哪些库呢?
在linux操作系统下 我们可以使用 ldd + 可执行文件名
来查看一个文件运行所依赖的库文件
这里面的 /lib64/lic.so.6
其实就是我们可执行程序所依赖的库文件
我们使用ls
指令查看该库文件 发现它是一个文件的软连接
紧接着我们使用ll
指令查看到该软连接的源文件
知道了该软连接的源文件之后我们可以使用file
指令来查看它具体的文件类型
我们可以看到 它是一个共享的目标文件库 (shared object)被用作动态连接 (dynamically linked)
怎么辨认linux下的动静态库?
.so
为后缀 静态库一般以.a
为后缀顺便提一嘴
.dll
为后缀 静态库一般以.lib
为后缀库的名字是什么呢?
我们看到的库的源文件名字是 libc-2.17.so
我们去掉前面它的标识符lib 去掉后面的后缀.so 之后剩下的就是它的名字
即库的名字为 c-2.17
在默认情况下 我们编译程序都是使用动态连接的
如果我们想要进行静态链接则需要在后面加上一个-static选项
进行静态链接之后我们可以明显发现可执行文件的大小比动态链接的可执行文件大小大得多
这是因为使用静态链接编译可执行文件的话编译器会将整个库打包到可执行文件当中
我们可以使用 ldd + 静态链接后的文件名
来证明这一点
此外我们还可以使用file
指令来查看它们是什么链接方式
动静态库的本质是可执行程序的“半成品”
我们都知道 语言从源文件变为可执行文件要分为下面四步
.i
文件.s
文件.o
文件比如说 如果我们想要将五个源文件变为一个可执行文件的话我们就应该这么做
首先将这五个文件全部汇编变成.o
目标文件
之后使用gcc编译器将这五个目标文件链接
而如果我们在别的项目中也需要用到这些二进制文件的话只需要复制一份过去之后用gcc编译器进行连接就可以
如果所有的项目几乎都要频繁的用到这几个目标文件呢?
那么我们实际上就可以对于这几个文件进行打包 放到环境配置中
每次要使用的时候自动到这个环境配置里面找
这样子打包后的文件就叫做库文件
所以说 各种动静态库的本质实际上就是一堆的目标文件(.o
文件)
它们当中不包含main函数而是包含了大量的方法供使用者调用
这就是动静态库的原理 所以我们可以说动静态库的本质是可执行程序的“半成品”
我们将动静态库和可执行文件的概念映射到现实生活中
你在宿舍中写作业 动态库就像是网吧中的电脑
当你遇到不会的题目的时候 你就会去网吧查资料
而静态库就像是你在宿舍中买的电脑
当你遇到不会的题目的时候 你就会使用自己的电脑去查资料
静态库
静态库链接就是程序在编译连接的将静态库中的代码下载到可执行文件当中
所以说我们使用静态库生成的可执行文件一般体积比较大
自然可执行程序运行的时候也不需要依赖外部库文件了
使用静态库链接之后 文件不需要依赖外部库文件也可以执行
使用静态库链接的库文件体积较大 占用空间多 且多个静态链接的可执行文件中可能会有大量的重复代码
动态库
动态库链接是程序在运行的时候才去链接相应的动态库代码 多个程序共享使用库的代码
在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存当中 这个过程被称为动态链接
为了节省空间 操作系统利用虚拟内存机制 将中间的一片共享区指向了物理内存中同一块动态库区域
节省磁盘空间 避免重复代码
依赖库文件运行 如果缺少库文件则无法运行
我们下面使用静态库打包和使用的例子让大家更加深入的理解静态库
我们使用下面的四个文件来演示
add.h
1 #pragma once 2 3 extern int myadd(int x , int y);
add.c
1 #include "add.h" 2 3 int myadd(int x , int y) 4 { 5 return x + y; 6 }
sub.h
1 #pragma once 2 3 extern int mysub(int x , int y);
sub.c
1 #include "sub.h"2 3 int mysub(int x , int y)4 {5 return x-y; 6 }
第一步 生成二进制文件
第二步 将所有目标文件打包为静态库
ar命令是gnu的归档工具 常用于将目标文件打包为静态库
下面是命令选项
我们可以使用下面的指令将一个或多个目标文件打包为静态库
ar -rc libst.a add.o sub.o
我们可以使用下面的指令查看一个静态库中的文件
ar -tv libst.a
第三步 将头文件和生成的动态库组织起来
与我们使用时一样
我们可以将所有的头文件放到 /include
文件夹中
将所有的静态库文件放到 /lib
文件夹中
接着将这两个目录放到 /mylib
中
这样子我们就可以将我们的mylib给别人使用了
我们首先写出一个源文件 main.c
1 #include
E> 2 #include
E> 3 #include 4 5 6 7 int main()8 {9 int x = 10; 10 int y = 20;
E> 11 int z1 = myadd(x , y);
E> 12 int z2 = mysub(x , y);13 printf ("myadd :: %d\n" , z1);14 printf ("mysub :: %d\n" , z2);15 return 0;16
我们发现 这个源文件中其实报了四个错误 这是因为我们c语言程序在查到头文件的时候只会查找同级的头文件还有系统配置的头文件
而我们当前目录下只有两个文件 main.c 和 mylib
不过我们不必担心 编译之后的程序肯定是可以正常运行的
使用方式一: 使用选项
gcc编译时我们需要使用的三个选项如下
所以说我们只需要敲出下面的指令他就可以
gcc -o main main.c -I ./mylib/include/ -L ./mylib/lib/ -l st
这段指令的意思是使用main.c源程序编译 指定某个头文件 指定某个库文件 指定st这个库
编译生成一个名为main的可执行文件
这个时候我们就可以运行我们的程序了
使用方式二: 把头文件和库文件拷贝到系统路径下
编译器之所以能够找到c的头文件和库文件是因为它们被放在默认的路径中
当我们开始编译的时候编译器就会默认去那里寻找
而头文件的默认路径在这里
/usr/include/
而库文件的默认路径在这里
/lib64/
所以说我们只需要将自己的头文件和库文件拷贝到这两个地方就可以了
我们使用下面两个指令来拷贝
cp ./mylib/include/* /usr/include/
cp ./mylib/lib/libst.a /lib64/
之后我们再看main文件代码
我们发现这次代码并没有报错
但是我们这里发现gcc编译的时候报错了 这是因为gcc编译的时候默认找的是c库 而我们添加的库并不是c库
所以说我们编译的时候还需要指定依赖的库文件
注意: 我十分不建议将自己写的库文件拷贝到系统路径中 这样子会污染原本的库文件
我们还是使用这四个库文件来演示动态库的打包和使用
第一步 生成对应的目标文件
让所有源文件生成对应的目标文件
动态库与静态库汇编指令中不同的是 动态库的汇编指令中需要加上-fPIC
选项
-fPIC
选项的意义是生成和位置无关码
它是一个很复杂的概念 涉及到很多的专业知识 这里就不过多赘述 对于它我们只需要了解两点
第二步 打包动态库
与生成静态库时使用ar命令打包不同 我们只需要使用gcc的-shared选项即可
指令如下
gcc -shared -o libdy.so add.o sub.o
第三步 将头文件和生成的动态库组织起来
和生成的静态库一样
我们可以将所有的头文件放到 /include
文件夹中
将所有的静态库文件放到 /lib
文件夹中
接着将这两个目录放到 /mylib
中
这样子我们就可以将我们的mylib给别人使用了
我们还是使用静态库的源文件main.c来演示动态库的使用
1 #include
E> 2 #include
E> 3 #include 4 5 6 7 int main()8 {9 int x = 10;10 int y = 20;
E> 11 int z1 = myadd(x , y);
E> 12 int z2 = mysub(x , y);13 printf ("myadd :: %d\n" , z1);14 printf ("mysub :: %d\n" , z2);15 return 0; 16 }
动态库的编译方法和静态库的编译方法一模一样
我们可以使用指令或者是将库文件和头文件放到系统目录中
我们这里使用指令为例
我们敲出下面的指令就可以编译
我们发现这样子就生成了我们的可执行程序了
但是和静态库的使用不一样 使用动态库的文件在成为可执行文件的时候还不能直接运行
我们再使用ldd指令 查看这个可执行文件所依赖的动态库
我们发现系统找不到这个动态库在哪里
这是因为 我们使用-I,-L,-l这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁 但是当生成的可执行程序生成后就与编译器没有关系了
所以说如果我们想要执行这个程序 还需要想办法告诉操作系统这个库文件的地址
这里有三种方式可以做到
方式一 拷贝到系统路径下
我们直接将该动态库拷贝到系统的库文件目录下 这样子操作系统就能够找到了
指令如下
cp ./mlib/dylib/libdy.so /lib64
这样子就可以顺利运行了
方式二 更改环境变量
LD_LIBRARY_PATH
是程序运行动态查找库时所要搜索的路径 我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH
环境变量当中即可
使用指令如下
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/shy/linux/lesson11~20/lesson13/code4/mlib/dylib/
之后我们再查询a.out的动态库 可以发现
我们可以发现动态库的路径变为了我们设置的路径
当然现在也可以继续运行了
方式三 配置/etc/ld.so.conf.d/
我们可以通过配置/etc/ld.so.conf.d/
的方式解决该问题
首先我们自己需要写一个以.conf
后缀的文件
将我们库文件所在的路径放到这个文件当中
之后将这个配置文件放到 /etc/ld.so.conf.d/
目录中
之后使用指令更新配置文件
ldconfig
这样子我们就可以运行我们的文件了