ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux中的静态库和动态库

Linux中的静态库和动态库

首先 我们要明白什么是库?

库(Library)是一组预编译的代码,提供特定的功能,可以被多个程序共享调用,避免重复编写代码。在链接步骤中,链接器将从库文件取得所需的代码,复制到生成的可执行文件中

Linux 中常见的库文件有两种,一种.a为后缀,为静态库,另一种以.so为后缀,为动态库。
静态库:在程序编译时,将库的代码“拷贝”到最终的可执行文件中;
动态库(共享库):在程序运行时,动态载入库的代码,多个程序可以共享同一份库文件。

静态库

静态库通常以.a为后缀(在Linux下),是多个目标文件(.o)组成的归档文件(Archive),里面存储着一组目标文件。

特点

  • 在链接(Linking)的时候被合并到可执行文件中
  • 可执行文件较大,因为库的代码已经复制到程序中
  • 加载快,无需在运行时再载入库文件
  • 无需在运行环境中存在库文件,便于部署

作用

  • 方便将多个目标文件打包成一个完整的可执行程序
  • 使用简单,不依赖外部共享库

创建静态库

# 先编译源文件生成目标文件
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o# 将目标文件打包为静态库
ar rcs libmylib.a file1.o file2.o# 编译时链接静态库
gcc main.o -L. -lmylib -o myprogram

使用静态库

  • 在编译时指定库路径和库名(-L和-l参数)
    • l:指定库名(库的文件名为 libxxx.a,库名为 xxx)
    • L:指定库路径
    • static:使用静态链接
gcc -static main.c -o main -l math -L ./
// 错误:gcc -l math -static main.c -o main -L ./

特别注意,必须把 -l math 放在后面。放在最后时它是这样的一个解析过程:

  • 链接器从左往右扫描可重定位目标文件和静态库
  • 扫描 main.c 时,发现两个未解析的符号 add 和 sub,记住这两个未解析的符号
  • 扫描 libmath.a,找到了前面未解析的符号,因此提取相关代码
  • 最终没有任何未解析的符号,编译链接完成
    那如果将 -l math 放在前面,又是怎样的情况呢?
  • 链接器从左往右扫描可重定位目标文件和静态库
  • 扫描 libmath.a,由于前面没有任何未解析的符号,因此不会提取任何代码
  • 扫描 main.c,发现未解析的符号 add 和 sub
  • 扫描结束,还有两个未解析的符号,因此编译链接报错
    如果把-l math放在前面,编译结果如下:

在这里插入图片描述

我们看看最终生成的文件大小:
在这里插入图片描述
生成可执行文件大小为892k
由于最终生成的可执行文件中已经包含了add和sub相关的二进制代码,因此这个可执行文件在一个没有libmath.a的Linux系统中也能正常运行。

动态库

动态库和静态库类似,但是它并不在链接时将需要的二进制代码都“拷贝”到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程。Linux 中这类库的名字一般是 libxxx.so。(shared object)

符号表

  • 符号表记录了函数和变量的名字、地址等信息,是连接器、加载器用来解决引用关系的重要数据结构。
  • 在动态库中,符号表包括:
    • 导出符号:库中提供给外部调用的函数和变量
    • 需要被其他程序或库引用的符号

重定位信息

  • 重定位是指在程序或库加载到内存后,动态调整代码中的地址引用,使得符号指向正确的内存地址。
  • 重定位信息记录了哪些位置需要修正(如函数调用、全局变量访问位置)
  • 在静态链接中,重定位在链接阶段完成;在动态链接中,加载时由系统完成。

定义:

  • 动态库也称“共享库”,以.so(Shared Object)为后缀
  • 在程序运行时动态载入,多个程序可以共享同一份库文件

特点

  • 在程序加载时才载入库(动态链接)
  • 可减小可执行文件大小,因为库在运行时加载
  • 便于维护和升级,只需替换库文件,无需重新编译所有程序
  • 运行时依赖,程序必须找到对应的.so文件

作用

  • 方便库的共享和维护
  • 可以实现插件式开发或模块化设计

创建动态库

# 编译生成共享库
gcc -fPIC -c file1.c -o file1.o
gcc -fPIC -c file2.c -o file2.o
gcc -shared -o libmylib.so file1.o file2.o# 编译程序链接动态库
gcc main.o -L. -lmylib -o myprogram

注意:

  • -fPIC:生成位置无关代码(Position Independent Code)
  • -shared:生成共享库

使用动态库

步骤命令示例说明
1. 创建共享库gcc -fPIC -c mylib.c -o mylib.o
gcc -shared -o libmylib.so mylib.o
生成.so文件
2. 编译调用程序gcc main.c -L. -lmylib连接到共享库
3. 设置环境变量或放库到系统路径export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
sudo cp libmylib.so /usr/lib/
让运行时找到共享库
4. 运行程序./myprogram运行程序,调用共享库中的函数

找不到的原因

原因类别描述解决措施
库路径未在标准路径或未指定库文件所在路径未加入系统路径或未在运行时指定设置LD_LIBRARY_PATH或rpath
缺少ldconfig刷新缓存添加新库后未刷新系统缓存运行ldconfig
编译参数不正确编译时未设置-rpath或路径错误使用-Wl,-rpath指定路径
依赖缺失库依赖的其他库不存在或路径错误用ldd检测,安装缺失的库
权限不足库文件权限限制修改文件权限

主要原因是系统在运行时无法定位到库文件的存放路径,可能因为库路径没有配置正确、库文件放错位置、未刷新缓存或依赖缺失等。

当在运行程序时,动态库(.so文件)找不到,系统会报错,导致程序无法正常启动。这种情况常见的错误信息有:

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

动态库找不到时的解决办法

1.临时设置环境变量LD_LIBRARY_PATH

这是一种快速方便的方式,临时告诉系统去哪里找共享库。

export LD_LIBRARY_PATH=.:/path/to/lib:$LD_LIBRARY_PATH
./your_program

示例:假设libmylib.so在当前目录:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./your_program

缺点:此设置只在当前终端有效,关闭终端后失效。

2.将共享库拷贝到系统标准目录

将你的共享库复制到系统的库目录(如/usr/lib或/usr/local/lib),然后刷新缓存。

sudo cp libmylib.so /usr/lib/
sudo ldconfig

说明:

ldconfig命令会刷新系统的库缓存,让系统知道新加入的库。

注意:需要管理员权限。

3.修改程序的RPATH或RUNPATH(在编译时指定)

在编译时,将库的路径提前嵌入到可执行文件中,使其在运行时自动查找。

例:

gcc main.c -L/path/to/lib -Wl,-rpath=/path/to/lib -lmylib -o myprogram
  • -Wl,-rpath=路径:在可执行文件中指定库搜索路径

执行后,无需设置LD_LIBRARY_PATH,系统会自动在指定路径查找库。

4.使用LD_LIBRARY_PATH环境变量文件(配置全局)

编辑/etc/ld.so.conf或在/etc/ld.so.conf.d/目录中添加自定义路径,然后运行

sudo ldconfig

这样,系统会在指定路径中查找库。

5. 动态调试:使用ldd命令检测缺失的依赖库

运行:

ldd ./your_program

可以显示程序依赖的所有共享库,查看是否有“not found”的库。

如果发现缺失的库,可以用上面的方法解决。

小结

方法作用适用场景
LD_LIBRARY_PATH临时指定库路径调试或快速测试
拷贝到系统目录将库放到标准路径,系统自动加载部署时,保证程序可以找到库
RPATH/RUNPATH在编译时嵌入路径内部控制,避免环境变量设置
ldconfig配置全系统范围配置库搜索路径长期维护或系统级别的配置
ldd命令检测依赖查看依赖关系,找出缺失的库调试、排错

静态库与动态库的区别

特性静态库(.a)动态库(.so)
链接时间编译时(静态链接)运行时(动态链接)
生成文件大小较大(所用到代码要从二进制文件中拷贝一份,复制到可执行文件中)较小(库文件单独存在,仅仅复制了一些重定位和符号表信息)
占用空间运行时每个程序都持有一份库的副本多个程序共享同一份库(内存节省)
升级维护需要重新编译程序只需替换库文件,无需重新编译
依赖关系不需要库文件存在于运行环境中需要库文件存在于运行环境中
运行速度略快(没有动态加载耗时)因为需要动态加载,可能略慢
返回列表