动态库和静态库


为什么我们要使用别人(一般是顶尖的工程师写的)的代码?

为了开发效率和鲁棒性(健壮性)

如何使用别人的功能?

1、库 2、开源代码 3、基本的网络功能调用(各自网络接口,语音识别)

库一般分为动态库和静态库,动态库一般的命名为libc.so,静态库一般的命名为libc.a,去掉前缀lib,去掉.之后的内容,剩下的就是库的名字,这里就是c库,生成可执行程序的方式有两种:动态链接和静态链接

#include<stdio.h>
int main()
{
    printf("hello world!\n");
    return 0;
}

ldd 可执行程序名字:查看可执行程序的动态链接关系

image-20220228135628014

在linux当中,默认情况下形成的可执行程序是动态链接的,可以看到这里是.so为后缀的

下面我们举个例子来解释一下动态链接和静态链接:

比如你在宿舍写作业,写到某个题突然不会了,学校旁边有个网吧,于是你就去网吧去查找资料解决问题,解决完了然后回到宿舍继续完成作用,还有一种方法就是你告诉你爸让给你买台电脑,你在宿舍有台电脑,于是你就不需要再去网吧去使用电脑去查阅资料,直接在自己买的电脑上面查找资料即可,前面说的那种方法类似于动态链接,后面的那种方法叫做静态链接

当调用库频率增加时或者在上面的例子中说去网吧查阅资料频繁时,就变得很麻烦,就和上面的例子一样我们自己买个电脑,这样就省事了不少。相对应的把库中的我的可执行程序中所需要使用的二进制代码,拷贝进我的可执行程序当中,这就叫做静态链接。

如果想使用静态链接,那就在gcc后面+static选项:一般,为了更好的支持开发,第三方库或者语言库,必须提供两个库,一个叫做静态库,一个叫动态库,方便程序员根据需要进行bin的生成

image-20220228142033129

动态链接的特点:体积小,节省资源(磁盘,内存),但是一旦库丢失,bin不可执行

静态链接的特点:体积大,浪费资源(磁盘,内存),不依赖库,库丢失不影响可执行程序

静态库与动态库的概念

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

理解动静态库

如何打包动静态库与如何使用动静态库

不想将源代码暴露给别人,就需要打包动静态库,下面我们来看如何打包动静态库:

我们首先写好自己想要打包的程序代码:

add.c

#include"add.h"
int myadd(int x,int y)
{
    return x+y;
}

add.h

#pragma once
#include<stdio.h>
int myadd(int x,int y);

sub.c

#include"sub.h"
int mysub(int x,int y)
{
    return x-y;
}

sub.h

#pragma once
#include<stdio.h>
int mysub(int x,int y);

我们想让别人能使用我的库,前提是别人需要首先知道你的库能给我提供什么方法,通过头文件体现,然后将源文件编译生成目标文件:

gcc -c add.c

生成.o文件,这个.o文件可以被别人链接

gcc -c sub.c

然后使用ar -rc命令生成静态库:

ar -rc libmymath.a add.o sub.o

我们只需要将头文件和这个刚生成的库文件给别人就好了,别人就可以用该库,形成自己的可执行程序

我们将头文件和这个刚生成的库文件都放进lib目录下,自己写个程序使用它:

#include<stdio.h>
#include"Add.h"
#include"sub.h"
int main()
{
    printf("%d\n",myadd(10,20));
    printf("%d\n",mysub(10,20));
    
    return 0;
}

这里会报错,找不到:image-20220228144018395

因为去当前路径或者默认路径(/usr/include)下找头文件,所以需要告诉gcc在指定路径下去找一下头文件:

-I选项:

gcc test.c -o mytest -I ./lib

我们发现还会有问题:

image-20220228144100170

我们需要告诉gcc除了默认路径(/lib64/libc)以及当前路径之外,在指定的路径下也找一下库文件:

-L(大写)选项:

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

-l(小写)库名称:具体你要链接哪一个库

image-20220228144231837

可以看到成功执行了

为什么C语言在编译的时候,从来没有明显的使用过-I -L -l等选项呢?

  1. 库文件和头文件在默认路径下gcc能找到
  2. gcc编译C代码,默认就应该链接libc

如果我们也不想使用这些选项呢?

头文件库文件分别拷贝到默认路径下,这个过程叫做库的安装,对于第三方库例如我们自己写的,一般也要带上-lname

如何制作打包动态库

生成动态库:

Makefile的编写

libmymath.so add.o sub.o
    gcc -shared -o $@ $^
add.o:add.c
    gcc -fPIC -c $^ 
sub.o:sub.c
    gcc -fPIC -c $^

gcc选项 -fPIC:产生与位置无关码

在进程地址空间中有共享区,一般动态库的代码是映射在共享区的,我们将库代码加载到内存,通过页表映射到进程地址空间的共享区,当其他程序想要相同的库代码时,可以通过页表映射到内存中相同库代码的部分,这样就大大减少了空间的使用情况,库有可能加载到物理内存的任意位置,也可能被映射到进程地址空间中共享区的任意区域,所以必须保证库当中的代码怎么执行都不会错,为了保证这个库当中的代码地址不随着加载到内存的位置发生变化而变化,需要告诉gcc,我们进行fPIC选项产生与位置无关码进行编译

我们通过静态库的那套方式进行编译链接:

gcc test.c -o mytest -I ./dy_lib -L ./dy_lib -lmymath

我们运行可执行程序,发现无法正常运行:

image-20220228153922235

此时和编译器gcc还有关系吗?毫无关系,是运行时出现的问题,属于运行问题,也要能够让系统帮我们找到运行时需要使用的动态库。

为什么之前动态链接的其他程序可以直接运行呢?因为库可以找到

环境变量LD_LIBRARY_PATH,需要将库的路径导入到环境变量LD_LIBRARY_PATH中:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/whb/101/lesson18/dy_lib/test/lib/

image-20220228154358535

可以看到此时就可以正常执行了

image-20220228154453152

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐