【知识讲解】 Linux之动静态库的了解


目录

前言

[Part1. 库的意义](#Part1. 库的意义)

[Part2. 静态库制作、编译与发布](#Part2. 静态库制作、编译与发布)

[Part2.1. 静态库制作步骤](#Part2.1. 静态库制作步骤)

[Part2.2. 使用静态库编译业务代码](#Part2.2. 使用静态库编译业务代码)

[Part2.3. 静态库的打包分发](#Part2.3. 静态库的打包分发)

[Part2.4. Makefile 自动化构建静态库、一键打包发布](#Part2.4. Makefile 自动化构建静态库、一键打包发布)

[Part3. 动态库制作、编译、部署](#Part3. 动态库制作、编译、部署)

[Part3.1. 动态库制作](#Part3.1. 动态库制作)

[Part3.2. 编译业务程序链接动态库](#Part3.2. 编译业务程序链接动态库)

[Part3.3. 解决运行时找不到动态库的方案](#Part3.3. 解决运行时找不到动态库的方案)

[Part3.4. 强制链接静态库](#Part3.4. 强制链接静态库)

[Part4. 静态库与动态库对比](#Part4. 静态库与动态库对比)

[Part5. 补充拓展](#Part5. 补充拓展)

[Part6. 结语](#Part6. 结语)


前言

Linux下对于动静态库的认识是非常重要的,接下来来跟随小编的视角来看看吧。


let's go!!!!!!!!


Part1. 库的意义

在工程开发中,我们会把可复用的功能封装成库,不需要每次都重新编译源码。


• 源码不需要对外交付,只需要交付头文件(.h,用于给用户声明函数) + 库文件(.a / .so,存放编译好的机器指令)

• 多个项目可以复用同一套库,大幅减少重复编译的成本

• 版本迭代时,只需要更新库文件,业务代码无需改动

补充:目标文件 .o 的价值:一个大型工程有上百个 .c 文件,如果只修改其中一个源码,只需要重新编译这一个文件生成 .o,其余目标文件可以直接复用,避免全量重新编译,节省编译时间。


编译链路:.c → 编译生成 .o(可重定位目标文件,ELF格式) → 链接 → 可执行程序 / 库

底层小知识:.o、静态库、动态库、可执行程序,全部都是ELF格式。ELF内部由大量的section(段)组成:代码段、数据段、只读段等;运行时操作系统会把多个section合并为segment(段分区),映射到进程的虚拟地址空间。


Part2. 静态库制作、编译与发布

静态库后缀命名规范:libxxx.a,比如我们示例库名字为 libmyc.a


Part2.1. 静态库制作步骤

1. 先把源码编译为目标文件 .o

cpp 复制代码
# -c:只编译,不链接,生成 *.o
gcc -c 1.c -o 1.o
gcc -c 2.c -o 2.o

2. 使用 ar 工具打包多个 .o,归档成为静态库 .a

cpp 复制代码
# ar -rc :r插入/替换,c创建归档文件
ar -rc libmyc.a 1.o 2.o

ar 本质就是一个归档工具,把一堆 .o 打包成一个文件,静态库本身就是一堆目标文件的压缩包,gcc链接的时候会把库里面用到的目标代码直接拷贝进最终的可执行文件。


Part2.2. 使用静态库编译业务代码

假设业务代码 main.c 需要调用我们静态库里面的函数。
编译链接语法:

cpp 复制代码
gcc -o exe main.c -I ./include -L ./mylib -l myc

参数解释:
参数 作用
-I路径 指定头文件搜索路径,用来找到 .h
-L路径 指定库文件(.a/.so)的搜索路径
-lxxx 链接名为 libxxx.a / libxxx.so 的库,自动补全lib前缀和后缀


Part2.3. 静态库的打包分发

我们交付给用户的时候,不需要交付 .c 源码,只需要交付:

• include/:存放 .h 头文件(函数声明)

• mylib/:存放编译好的 libmyc.a 静态库

用户拿到之后,有两种方式使用头文件:

1. 编译时通过 -I 指定头文件目录

cpp 复制代码
gcc -c main.c -I ./lib/include

2. 或者直接把头文件拷贝到系统标准头文件目录 /usr/include

cpp 复制代码
cp lib/include/* /usr/include

库文件拷贝到系统库目录 /usr/lib

cpp 复制代码
cp lib/mylib/libmyc.a /usr/lib

之后编译就可以简化,不需要写 -I -L,直接:

cpp 复制代码
gcc -o exe main.c -l myc

Part2.4. Makefile 自动化构建静态库、一键打包发布

可以用Makefile自动化完成编译、打包、创建目录、打包压缩交付包

cpp 复制代码
libmyc.a: 1.o 2.o
    ar -rc $@ $^

1.o:1.c
    gcc -c $< -o $@

# 发布目标:自动创建目录,拷贝头文件、库,打包tar压缩包

cpp 复制代码
.PHONY: output
output:
    mkdir -p lib/include
    mkdir -p lib/mylib
    cp -f *.h lib/include
    cp -f libmyc.a lib/mylib
    tar -czf lib.tgz lib

执行 make output,直接生成 lib.tgz,这个压缩包就可以发给别人使用。

静态库核心特点:链接的时候,代码直接复制进exe。一旦编译完成,后续静态库文件就算删除,程序依旧可以正常运行。


Part3. 动态库制作、编译、部署

动态库后缀:.so,命名示例 libmyc.so

动态库不会把代码拷贝进可执行文件,程序运行的时候,操作系统才去加载 .so,多个进程可以共享同一份动态库在内存中的副本,节省内存。


Part3.1. 动态库制作

1. 编译目标文件,必须带上 -fPIC,生成位置无关代码

cpp 复制代码
gcc -fPIC -c 1.c -o 1.o
gcc -fPIC -c 2.c -o 2.o

-fPIC:Position Independent Code 位置无关代码,生成的机器指令没有硬编码绝对地址,可以被加载到虚拟地址空间的任意位置,是动态库的核心。静态库不需要 -fPIC。

2. 使用 -shared 参数,把 .o 链接打包为动态库 .so

cpp 复制代码
gcc -shared -o libmyc.so 1.o 2.o

Part3.2. 编译业务程序链接动态库

编译链接阶段语法和静态库几乎一模一样:

cpp 复制代码
gcc -o exe main.c -I ./include -L ./mylib -l myc

这里有一个非常经典的坑:编译链接成功,但是运行的时候报错,找不到动态库!

原因:-L 只是编译阶段告诉gcc去哪里找库;程序运行阶段是操作系统去加载动态库,和gcc已经无关了,操作系统有自己一套动态库搜索路径。


Part3.3. 解决运行时找不到动态库的方案

方案1:临时环境变量 LD_LIBRARY_PATH

LD_LIBRARY_PATH 是Linux操作系统的动态库环境变量,操作系统运行程序的时候,会优先从这个变量记录的目录里面搜索 .so。

cpp 复制代码
# 临时生效,当前终端窗口有效,关闭终端就失效
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xxx/lib/mylib
./exe

方案2:写入系统动态库配置文件(永久生效)

Linux系统动态链接器会读取 /etc/ld.so.conf.d/ 下面的配置文件

1. 在 /etc/ld.so.conf.d/ 新建一个配置文件,随便命名,例如 myconfig.conf,写入我们动态库的绝对路径
/home/xxx/lib/mylib

2. 更新动态链接器缓存ldconfig
永久生效,所有用户、所有终端都可以找到这个动态库

方案3:直接拷贝动态库到系统标准库目录 /usr/lib

cpp 复制代码
cp libmyc.so /usr/lib
ldconfig

Part3.4. 强制链接静态库

如果你本身系统同时存在 .a 和 .so,gcc默认优先链接动态库。
如果你想要强制编译的时候使用静态库,增加 -static 参数

cpp 复制代码
gcc -o exe main.c -I ... -L ... -l myc -static

Part4. 静态库与动态库对比

特性 静态库 .a 动态库 .so
链接时期:编译链接阶段,代码拷贝进可执行文件 编译阶段只记录符号,运行时操作系统加载

程序体积:可执行文件更大,包含库代码 可执行文件更小

内存占用:每个进程都有一份独立的库代码副本,内存占用高 内存中只加载一份,多进程共享,节省内存

更新方式:更新库,业务代码必须重新编译链接 替换 .so 文件即可,不需要重新编译业务程序

依赖:编译完成后不再依赖原 .a 文件 运行时必须保证 .so 存在,否则程序无法启动

工程选型:Linux后端服务绝大多数场景优先使用动态库;一些嵌入式、想要单文件交付、不想携带一堆so的场景会使用静态库。


Part5. 补充拓展

1. .o 可重定位目标文件:编译产物,还没有分配虚拟地址,等待链接器完成地址重定位

2. ELF 文件内部划分为很多 section:.text代码段、.data数据段、.rodata只读段、.bss未初始化数据段

3. 当程序被操作系统加载运行时,内核会把多个section合并成segment内存段,映射到进程独立的虚拟地址空间,这就是进程的内存布局。


Part6. 结语

这篇文章我们深入的知道了动静态库的相关知识,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

相关推荐
Fcy6481 小时前
Linux下 五种IO模型的认识与⾮阻塞IO的实现
linux·服务器·io
Mikko72 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
书生执笔画浮沉2 小时前
分享一个开源小工具CAdapt
linux·c·gnu·glibc·kernel·升级·灵感
光依旧2 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
mounter6252 小时前
Linux 内核热升级(Live Update)技术详解:PCI、VFIO 与 IOMMU 子系统的演进与实战解析引言
linux·linux kernel·kernel·iommu·vfio
砚凝霜3 小时前
【软考信息安全】第十九章 Unix/Linux安全分析与防护
linux·安全·unix
yichengerp3 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦3 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
creator_Li3 小时前
Linux 的 Page Cache
linux·page cache