Linux动态库 vs 静态库:创建步骤与优缺点对比

Linux系列


文章目录


前言

在Linux系统开发中,静态库和动态库是我们经常使用的两种库文件类型,使用库文件进行开发工作,可以大大提高我们的开发效率,学习动静态库的差异及使用方式是我们熟练运用它的前提。

本篇文章我们将通过自己制作动静态库,来深入的学习动静态的使用、差异、原理。


一、动静态库的概念引入

在我们学习语言的过程中,不可避免的会使用库函数,那么它到底是什么呢?又是如何实现的呢?

接下来我们会慢慢回答这个问题。

1.1 库的基本概念

库是用来实现代码共享的,是预编译的代码集合(.o文件的集合),用来提供可复用的函数、类或数据,从而简化我们的开发过程提高开发效率,当我们使用库中的代码时只需要对库链接就可以使用。库又分为动态库和静态库两种类型,这两者各有其特点。

1.2 静态库(Static Library)

静态库通常由多个.o文件打包形成,以.a为后缀的文件集合。当可执行程序链接静态库时,在程序编译阶段会将使用到的,库中的程序所在的.o文件,全部嵌入至可执行文件中。

静态库的特点:

  • 文件编译时会将程序所用到的代码所在的.o文件全部嵌入至可执行文件中。
  • 每个可执行文件相对库来说是独立的(依赖代码已嵌入可执行文件)。
  • 不需要在程序运行时进行额外的文件加载。
  • 链接静态库会增加可执行文件的体积。
  • 库文件更新时需要重新编译所依赖该库的可执行文件。

1.3 动态库(Dynamic Library)

动态库比较复杂,我会在本片后面,及其后续文章不断补充相关知识

动态库是在程序运行时再加载的共享库,当程序执行时通过链接器与动态库建立关系,这个过程并不会发生拷贝。动态库同样是以多个.o文件打包的,以.so为后缀的文件集。

动态库的特点:

  • 程序运行时由操作系统动态加载、链接。
  • 一份被加载到内存的动态库,可被多个程序共享,减少了内存、磁盘的空间消耗。
  • 程序不需要额外内存,相较于链接静态库的可执行文件所占内存较小。
  • 更行动态库不需要重新编译可执行文件。

1.4 动静态库的核心区别

特性 静态库 (.a) 动态库 (.so)
链接方式 编译时直接嵌入到可执行文件中 运行时由动态链接器(如 ld-linux.so加载
文件体积 可执行文件较大(包含库代码拷贝) 可执行文件较小(仅记录引用)
内存占用 每个进程独立加载库代码,内存冗余 多个进程共享同一份库内存
更新维护 需重新编译程序 替换 .so 文件后立即生效
依赖管理 无外部依赖,独立运行 需确保运行时库路径正确
加载速度 启动快(无需加载库) 启动稍慢(需加载库)

静态库: 静态库的链接发生在可执行程序的编译时。编译器会将可执行文件中所涉及库中的代码,对应的.o文件嵌入到可执行文件中。
动态库: 动态库的链接发生在程序运行。动态库的链接会在程序启动时将动态库加载至内存的共享区,并库代码链接至可执行程序中(具体行为会在下篇文章中详细介绍)。

二、动静态库的实现

静态库和动态库本质都是.o文件的集合,只是在应用层两者的库文件的打包方式及访问方式存在差异,下面我们使用简单的代码,感受库的创建及使用,来深入学习。

在此过程中我们会以库的制作者和使用者两个视角来感受

2.1 静态库的创建及使用

代码部分:

c 复制代码
  #pragma once   //防止头文件被重复包含                                                                                                                   
  #include<stdio.h>
  extern int myerrno;//标识该变量在源文件中已声明,具体作用可以搜一下
  int mydiv(int x,int y);

上面代码为mymath.h文件代码,学到这里我们就必须知道了,在我们编写代码时包含的.h文件(头文件)其实就是相当于库函数的一份说明书,内部并不存在具体函数实现。

cpp 复制代码
  #include"mymath.h"
  int myerrno=0;
  int mydiv(int x,int y)
  {                                                                                                                                 
    if(y==0)
    {
      myerrno=1;//防止除0错误
      return -1;
    }
    return x/y;
   }

上述代码为mymath.c文件内容。

c 复制代码
  1 static-lib=libmymath.a
  2 $(static-lib):mymath.o
  3     ar -rc $@ $^
  4 mymath.o:mymath.c
  5     gcc -c $^
  6 .PHONY:output
  7 output: 
  8    mkdir -p mylib/include
  9    mkdir -p mylib/lib
 10    cp *.h mylib/include
 11    cp *.a mylib/lib
 12 
 13 .PHONY:clear
 14 clear:
 15     rm -rf *.o *.a  mylib        

上面代码为Makefile文件内容,我来简单的解释一下:

2~3:使用ar -rc指令将.o文件打包成名为libmymath.a的静态库。

6~12:模拟库的发布,供使用者使用(Linux中也存在,存储库及头文件的目录)。

创建出我们的静态库并将库发布。

使用者将别人的库安装到自己所在路径。

c 复制代码
   #include<mymath.h>
   int main()
   {
     int tmp=mydiv(10,1);
     printf("%d\n",tmp);
     return 0;
   }       

上面为main.c文件内容。

注意到了这里我们的库还是无法做到,像使用系统中的默认库那样使用,当你编译此文件时会发生链接错误,当我们的程序执行时找不到mymath.h的内容,这点很容易理解,他们都不在同一路径下,那么为什么c语言的库函数就可以直接编译呢?在Linux系统中,当我们在指向一个程序时,系统默认会去/usr/include/路径下寻找程序所包含的头文件,会去/lib64/路径下去找程序所需要的库文件,所以要想我们的库可以像c库一样被使用,只需将我们所写的libmymath.a库和mymath.h拷贝到上述系统路径即可,这里我们就不演示了,下面我们介绍另一种方法:

bash 复制代码
gcc 目标文件 -I 指定头文件搜索路径 -L 指定库文件搜索路径 -l指定链接库文件
bash 复制代码
gcc main.c -I mylib/include/ -L mylib/lib/ -lmymath

这里需要注意的是,当我们指定库文件是,只需要库文件名即可:lib库文件名.a。

程序成功编译并执行,此时a.out可执行文件中,已经嵌入一份mymath.o文件的数据了,即使你将库删除文件仍能执行,这里我就不演示了。

2.2 动态库的创建和使用

这里我们着重讲解两者不同的部分

代码部分:
mymethod.h文件内容:

c 复制代码
  1 #pragma once
  2 #include<stdio.h>                                                                                                                 
  3 int print(char *str);    

mymethod.c文件的内容:

c 复制代码
  1 #include"mymethod.h"
  2 
  3 int print(char *str)                                                                                                              
  4 {
  5   printf("%s\n",str);
  6   return 0;
  7 }

Makefile文件的内容:

c 复制代码
  1 dy-lib=libmymethod.so
  2 $(dy-lib):mymethod.o
  3     gcc -shared -o $@ $^
  4 mymethod.o:mymethod.c
  5     gcc -fPIC -c $^
  6 .PHONY:output
  7 output: 
  8    mkdir -p mylib/include
  9    mkdir -p mylib/lib
 10    cp *.h mylib/include
 11    cp *.so mylib/lib
 12 .PHONY:clear
 13 clear:
 14     rm -rf *.o  *.so mylib        

2~3:将.o文件打包变为动态库,与静态库的打包方式不同。

4~5:将.c文件编译为.o文件
-fPIC:生成位置无关码,会在下篇介绍

接下来我们直接将库生成,并发布,以使用者角度再次探讨:

c 复制代码
    1 #include<mymethod.h>
    2 
    3 int main()
    4 {                                                                                                                               
    5   print("abcdefg");
    6   return 0;
    7 }


现在我们使用程序链接静态库一样的方式,链接了动态库并生成了可执行文件,但是如果我们仅做了上面这些行为,生成的可执行程序还是无法执行,这是因为不同于静态链接的行为(将库嵌入到可执行文件),动态链接在链接时只是记录依赖关系,真正的加载是在原型时。所以即使用户在编译时正确指定了-L-l选项,生成的可执行文件在运行时仍然找不到动态库。

解决程序找不到动态库的方法:

1、将库文件和头文件拷贝到系统默认的库路径/lib64/usr/include下(系统默认链接搜索路径)。

2、在上述路径下与我们的库文件建立软连接(软连接的相关只是,上篇我们)。

3、将自己的库所在路径,添加到系统的环境变量LD_LIBRARY_PATH中。

4、在/etc/ld.so.conf.d路径下建立自己的动态路径的配置文件,然后重新ldconfig即可。

上述方法大家可以自己尝试,这里就不做展示了

三、优缺点对比

静态库
  • 优点
    • 无运行时依赖,部署简单。
    • 启动速度快(无需加载库)。
  • 缺点
    • 可执行文件体积大,浪费磁盘和内存。
    • 更新需重新编译,维护成本高。
动态库
  • 优点
    • 节省磁盘和内存资源。
    • 支持热更新(替换 .so 文件即可)。
  • 缺点
    • 依赖管理复杂(需设置 LD_LIBRARY_PATHrpath)。
    • 版本冲突风险(如 libfoo.so.1libfoo.so.2 不兼容)。

总结

本篇文章我们就介绍到这里,需要补充的是,gcc在编译程序时默认链接动态库,如果想要链接静态库可以使用-static显示指定。对于动态如如何实现共享,以及如何加载到内存这点我会在下篇介绍。

相关推荐
用手码出世界3 分钟前
【Linux】进程池bug、命名管道、systemV共享内存
linux·运维·bug
正点原子1 小时前
【正点原子STM32MP257连载】第二章 ATK-DLMP257B使用前准备 #串口软件 #MobaXterm
linux·stm32·单片机·嵌入式硬件
zhaoyqcsdn1 小时前
C++对象池设计:从高频`new/delete`到性能飞跃的工业级解决方案
c++·经验分享·笔记
CppPlayer-程序员阿杜1 小时前
poll为什么使用poll_list链表结构而不是数组 - 深入内核源码分析
网络·c++·链表·list·poll
@hdd1 小时前
C++| 深入剖析std::list底层实现:链表结构与内存管理机制
c++·链表·list
life_time_1 小时前
C语言-习题整理(1)
c语言·开发语言
MobiCetus2 小时前
Linux Kernel 7
linux·运维·服务器·windows·ubuntu·centos·gnu
西洼工作室2 小时前
centos时间不正确解决
linux·运维·centos
weixin_428498492 小时前
使用POCO库进行ZIP压缩和解压
c++
摆烂能手2 小时前
C++基础精讲-06
开发语言·c++