【Linux】 基础IO(动静态库的制作与使用)--万字详解

引言:

我们为什么要使用别人的代码?

说白了就两点:开发更快、程序更稳。

别人已经把功能写好了,我们直接拿来用,省时省力。而且这个"别人"通常不是普通人,而是顶级的工程师------也就是说,如果你的程序出了bug,大概率是你自己的问题,而不是库的问题。这就叫健壮性。

换个角度看:我负责写调用逻辑,别人负责写底层库,两人分工明确、各自擅长。这样合作出来的程序,既高效又稳定。

什么是健壮性? 简单说就是:你在百度搜一堆乱码,百度没崩------这就是健壮。

一.什么是库

库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个⼈的代码都从零开始,因此库的存在意义⾮同寻常。本质上来说库是⼀种可执⾏代码的⼆进制形式,可以被操作系统载⼊内存执⾏。

库有两种:

  • 静态库 .a[Linux]、.lib[windows]
  • 动态库 .so[Linux]、.dll[windows]

库的名字怎么来的?去掉前缀 lib,再去掉 . 及后面的内容,剩下的就是库名。比如 libc.so → 库名就是 c

怎么用别人的代码?

编译链接有两种方式:静态链接动态链接,对应的就是静态库动态库

举个例子:

大一新生张三想上网,但不知道哪有网吧,就去问学长。学长告诉他地址,他就跑过去上了几个小时,然后回来继续写作业。------这叫动态链接(需要的时候去找)。
两年后张三成了大三学长,家里给买了台电脑。想上网直接开机就行,不用再跑出去了。------这叫静态链接(自己就有,不依赖外界)。
C/C++ 中怎么用别人的代码?

说白了就是两种方式:静态链接动态链接,分别对应静态库(.a)和动态库(.so)。

举个例子


动态链接像去网吧上网:

大一新生小明想上网,但自己没电脑,只能问学长网吧在哪,然后跑过去玩几个小时再回来。每次上网都得依赖网吧存在,网吧关门他就没得玩。

静态链接像自己买了台电脑:

大三时家里给他买了电脑,想上网直接开机就行,不用再跑出去找网吧了。自己的电脑随时能用,不依赖外部。

为什么大多数程序用动态链接?

第一,编译快。 动态链接不需要把库代码拷进程序里,编译时只记个地址就行,省时间。

第二,省空间。 就像装 VS2022 时,你需要什么组件就下载什么,而不是一股脑全装进来。动态库也是这个道理,用到了才加载,没用到就不占地方。

静态链接什么时候用?

服务器部署场景。

虽然服务器上也大多是动态链接,但有时候需要把服务部署到很多台机器上。动态链接有个风险:程序运行后才去加载库,万一某台机器上少了某个库,程序就直接崩溃。所以有些程序会选择静态链接,把所有东西都打包进可执行文件里,不依赖任何外部库,拷到哪都能跑。

代价就是体积大。 静态链接的程序可能比动态链接的大几十倍甚至上百倍,下载慢、占磁盘。

两种方式各有好坏,没有绝对的对错,看场景选就行。

表格总结

方式 像什么 优点 缺点
动态链接 去网吧上网 编译快、省空间 依赖外部库,库丢了就崩
静态链接 自己买电脑 不依赖外部,随便拷 体积大、编译慢

两种方式的对比

特性 动态链接 静态链接
什么时候链接 程序运行时 编译时
要不要依赖库 不要
程序体积
库丢了能跑吗 不能
多程序共享 是,内存里共用一个 否,各用各的

静态库(.a):在编译链接阶段,库的代码就被直接拷贝进可执行文件中。程序运行时,不再需要这个库文件。
动态库(.so):在程序运行时,才去加载库的代码。而且多个程序可以共享内存中的同一份库代码。

动态链接详解

动态链接生成的程序里,只存了一张"函数地址表",而不是整份库代码。程序启动时,操作系统才会把真正需要的库代码从磁盘加载到内存。

这个过程就叫动态链接

优点:程序小、省磁盘、省内存。多个程序可以共用内存里的同一份库代码。

缺点:依赖库文件。万一库丢了或者没装,程序就直接挂。


静态链接详解

静态链接生成的程序,在编译阶段就已经把库代码直接拷贝进了可执行文件里。程序运行时,不再需要任何库文件。

优点:不依赖外部库,拷到哪都能跑。

缺点:程序体积大,编译慢。一份库代码被多个程序各拷贝一份,浪费空间。

看一下代码:

cpp 复制代码
// ubuntu 动静态库
// C
$ ls -l /lib/x86_64-linux-gnu/libc-2.31.so
-rwxr-xr-x 1 root root 2029592 May 1 02:20 /lib/x86_64-linux-gnu/libc-2.31.so
$ ls -l /lib/x86_64-linux-gnu/libc.a
-rw-r--r-- 1 root root 5747594 May 1 02:20 /lib/x86_64-linux-gnu/libc.a
//C++
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -l
lrwxrwxrwx 1 root root 40 Oct 24 2022 /usr/lib/gcc/x86_64-linux-
gnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
// Centos 动静态库
// C
$ ls /lib64/libc-2.17.so -l
-rwxr-xr-x 1 root root 2156592 Jun 4 23:05 /lib64/libc-2.17.so
[whb@bite-alicloud ~]$ ls /lib64/libc.a -l
-rw-r--r-- 1 root root 5105516 Jun 4 23:05 /lib64/libc.a
// C++
$ ls /lib64/libstdc++.so.6 -l
lrwxrwxrwx 1 root root 19 Sep 18 20:59 /lib64/libstdc++.so.6 ->
libstdc++.so.6.0.19
$ ls /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a -l
-rw-r--r-- 1 root root 2932366 Sep 30 2020 /usr/lib/gcc/x86_64-redhat-
linux/4.8.2/libstdc++.a

二.静态库制作(打包+使用)(不使用makefile)

1.预备工作

别人要使用我们写的接口,最直接的方式就是把源文件(.c)和头文件(.h)一起给对方。对方拿到后,在当前目录下直接 #include 就能用。

但这样有个问题:源代码全暴露了

如果我不想让别人看到我是怎么实现的,只想告诉他们"这个函数是干什么的、怎么用",那就要用到

库的本质,就是隐藏实现、暴露接口。


怎么做到呢?

  • 头文件(.h):用来暴露声明。告诉用户"我有这些函数,参数是什么,返回值是什么"。

  • 库文件(.a / .so):用来隐藏实现。把源代码编译成二进制格式打包起来,用户看不到源码,但可以链接使用。

所以,给别人用库的时候,需要提供两东西:

  1. 头文件 ------ 告诉用户怎么调用

  2. 库文件 ------ 让用户链接进去用

光有头文件不行(只有声明没有实现),光有库文件也不行(不知道函数怎么用)。两者配合,才能用。

2.生成静态库

2-1制作静态库的命令

cpp 复制代码
 1. 编译成目标文件
gcc -c log.c -o log.o
gcc -c my_math.c -o my_math.o

 2. 打包成静态库(库名叫 util)
ar -rc libutil.a log.o my_math.o

 3. 编译测试程序并链接静态库
gcc main.c -I. -L. -lutil -o static_test

 4. 运行
./static_test
  1. 编译成目标文件

gcc -c log.c -o log.o

gcc -c my_math.c -o my_math.o

先将源文件汇编后生成 .o 文件,,但它还不可以执行,因为还差最后一步链接。所以库就是在编译过程中,在链接的前一步停下来,编译成可被链接的目标文件。

问题来了: .h + .o 就可以给直接别人用?

答案是可以的

因为 .o 文件已经是编译后的二进制机器码,只是还没链接成最终的可执行文件。

.o 文件已经是机器码,链接器可以直接把它和用户的代码拼在一起。所以只要给头文件(告诉怎么调)和 .o 文件(给实现),别人就能用,而且看不到源码。

提供方式 别人能否看到源码 能否用 说明
.h + .c 源码全暴露
.h + .o 不能 隐藏实现,只给二进制
.h + .a(静态库) 不能 .a 就是多个 .o 的打包
.h + .so(动态库) 不能 运行时加载
  1. 打包成静态库(库名叫 util)

ar -rc libutil.a log.o my_math.o

ar 是 GNU 的归档工具,rc 表示 replace and create:意思就是如果要生成的库中已经包含了对应的 .o 文件,就 replace,否则就 create。

  • 如果生成的库中已经存在同名 .o 文件,就替换

  • 如果不存在,就创建

执行 ar -rc libutil.a my_math.o log.o 后,libutil.a 就是最终的静态库。


交付给别人使用只需要提供三样东西:

  • my_math.h(头文件,告诉别人有什么函数)

  • log.h(头文件)

  • libutil.a(静态库,里面是二进制实现)

别人拿到后,就能直接编译链接使用,但看不到你的源代码


静态库的本质

把源代码编译成 .o 文件(不链接),再把所有 .o 打包成一个 .a 文件。

用户拿到的是 头文件(接口说明) + 库文件(二进制实现),源码不需要给。


举个例子

我们平时写 C 代码时,#include <stdio.h> 然后直接调 printf,最后链接就能跑。

为什么?因为系统在安装的时候,就已经把:

  • 头文件(stdio.h 等)装到了 /usr/include/

  • 库文件(libc.alibc.so)装到了 /usr/lib/

所以不需要拿到 C 标准库的源代码,照样能用。

这就是静态库的使用方式:头文件 + 库文件。


注意

静态库的命名必须以 lib 开头,以 .a 结尾。

比如 libutil.a,库名叫 util(去掉前缀 lib 和后缀 .a)。

查看静态库中的目录列表

ar -tv libutil.a

运行发现这个时间是1970不是现在的实时更新时间

解决办法

方法一:重新打包(加 -U 选项,用实际文件时间)

bash 复制代码
ar -rcU libutil.a log.o my_math.o

方法二:重新生成 .o 再打包​​​​​​​

cpp 复制代码
touch log.c my_math.c
gcc -c log.c -o log.o
gcc -c my_math.c -o my_math.o
ar -rc libutil.a log.o my_math.o

方法三:忽略它

反正 不影响使用,只是看着别扭。链接时完全正常。

总结

ar -tv 显示 1970 年只是打包时的时间戳丢了,不影响静态库的功能,重新用 ar -rcU 打包就能恢复正常显示。


2-2使用静态库

mkdir -p

mkdir = 创建目录(make directory)

-p = 如果父目录不存在,就自动创建(parent)

bash 复制代码
mkdir -p hello/include

这条命令的意思是:创建 hello/include 目录。

  • 如果 hello 目录不存在,-p 会先创建 hello,再在里面创建 include

  • 如果没有 -p,而 hello 不存在,就会报错


cp

cp = 复制文件或目录(copy)

基本用法

cp 源文件 目标位置

例子

bash 复制代码
cp log.h hello/include/

意思是:把 log.h 复制到 hello/include/ 目录下。
hello/ 复制给李四(上一级目录的 lisi/

bash 复制代码
cp -rf hello ../lisi/

切换到李四的目录

bash 复制代码
cd ../lisi
ls

应该看到 hello/ 目录。

bash 复制代码
如果 hello/ 里面是空的,或者没有 include/ 和 lib/,就需要先整理:

# 创建目录
mkdir -p hello/include
mkdir -p hello/lib

# 复制头文件
cp log.h my_math.h hello/include/

# 复制静态库
cp libutil.a hello/lib/

lesson13 目录下完成了静态库 libutil.a 的制作(包含 logmy_math 两个模块),并整理成 hello/include(头文件)和 hello/lib(库文件)的交付目录;李四拿到后,通过 -I./hello/include 指定头文件路径、-L./hello/lib 指定库路径、-lutil 链接库,成功编译运行,全程无需暴露源代码。

我的错误

  • 链接错误(不是编译错误)

  • 编译器找到了头文件(#include 没问题)

  • 但找不到函数的实现代码(库没链接进来)

原因

  • 只写了 gcc main.c,没告诉编译器要链接 libutil.a

  • 需要加上 -L./hello/lib -lutil

解决方法:

bash 复制代码
gcc main.c -I./hello/include -L./hello/lib -lutil

-I → 编译失败(头文件找不到)

头文件 log.h 既不在当前目录,也不在系统默认路径。编译器找不到它,所以报错 fatal error。解决方法是加 -I 告诉编译器去指定目录找头文件。
报错 main.c: No such file or directory 表示当前目录下找不到 main.c 源文件。这不是 -I 的问题,-I 只负责找头文件。需要先用 cd 进入 main.c 所在目录,或者在 gcc 命令中指定 main.c 的路径。

-I-l → 链接失败(函数未定义)

加了 -I 后,头文件找到了,编译阶段通过。但链接阶段找不到 PrintWithTimesumRangeisEven 这三个函数的实现(它们放在库文件里)。解决方法是加 -L 指定库的路径,加 -l 指定库的名字。

此时我们切换了目录,不是之前的lisi而是现在的lesson13因为你的lisi就没有main.c

原因: 编译阶段过了(头文件找到了),但链接阶段找不到函数的实现(没链接库)。

解决方法:-L-l

-I-L,但缺 -l(还是链接失败)

-L 只是告诉链接器"去这个目录找库",但没有说具体要链接哪个库。如果目录下有多个库,链接器不知道用哪一个。所以还是报 undefined reference。解决方法是再加 -l 指定具体的库名。
原因: -L 只是告诉链接器"去这个目录找库",但没说具体要链接哪个库。

解决方法:-l

-I-L-l,成功

复制代码
gcc main.c -I./hello/include -L./hello/lib -lutil -o my_program
  • -o my_program 指定了输出文件名

  • 生成 my_program


复制代码
gcc main.c -I./hello/include -L./hello/lib -lutil
  • 没有指定输出文件名

  • 默认生成 a.out


./my_program进行执行

-I 找到头文件,-L 找到库的目录,-lutil 告诉链接器链接 libutil.a(库名前缀 lib 和后缀 .a 可省略)。三者齐全,编译链接成功,程序正常运行。


注意:需要提前将库拷贝到系统的默认路径下,这个过程叫作库的安装。


-I 的作用

-I 后面跟一个路径,作用是:告诉 gcc 除了默认路径和当前路径之外,也去这个指定的路径下找头文件。


系统的默认头文件路径

Linux 下头文件的默认路径主要是 /usr/include

在这个目录下,你可以找到最熟悉的 C 语言头文件,比如:

  • stdio.h

  • stdlib.h

  • string.h

  • time.h

  • math.h


补充说明

  1. 还有其他默认路径

    除了 /usr/include,系统还可能在其他路径下搜索头文件,比如:

    • /usr/local/include

    • /usr/include/x86_64-linux-gnu/(Ubuntu 特有)

  2. 不同发行版路径有差异

    CentOS 和 Ubuntu 的头文件路径组织不完全一样,但 /usr/include 是通用的。

-L 的作用

-L 后面跟一个路径,作用是:告诉 gcc 除了默认路径和当前路径之外,也去这个指定的路径下找库文件。


系统的默认库路径

Linux 下库文件的默认路径通常是:

  • /lib64

  • /usr/lib64

  • /lib

  • /usr/lib

在这些目录下,你可以找到熟悉的 C 语言库,比如 libc.a(静态库)和 libc.so(动态库)。


补充说明

需要说明两点:

  1. 实际搜索路径不止这些

    系统在查找头文件和库文件时,除了当前目录和上面提到的默认路径,还有其他一些默认路径,比如 /usr/local/lib 等。

  2. 不同发行版路径有差异

    不同的 Linux 发行版(甚至同一发行版的不同版本)在路径组织上可能不一样。例如 CentOS 常用 /usr/lib64,而 Ubuntu 常用 /usr/lib/x86_64-linux-gnu/

-l + 库名

-l 的作用

-l 后面跟库名,作用是:告诉链接器要链接哪一个具体的库。


库名的写法规则

写法 说明
实际库文件名 libutil.alibutil.so
-l 后面写的 util(去掉前缀 lib 和后缀 .a / .so

举例说明:

  • 库文件 libutil.a-lutil

  • 库文件 libc.so-lc

  • 库文件 libm.a-lm

为什么需要 -l

  • -L 只是告诉链接器"去哪个目录找库"

  • 但一个目录下可能有多个库文件,链接器不知道你要用哪一个

  • -l 就是明确告诉链接器:我要链接这个库


补充说明

  1. 优先链接动态库

    如果同一目录下同时存在 libutil.alibutil.so,gcc 默认优先链接动态库(.so)。

  2. 可以链接多个库

    如果需要链接多个库,可以写多个 -l:(这里涉及线程的知识后面会讲)

    bash 复制代码
    gcc main.c -L./lib -lutil -lm -lpthread
  3. 库的顺序有讲究

    如果库之间有依赖关系,被依赖的库要放在后面。

3.库搜索路径的顺序

链接器在查找库文件时,按照以下顺序从左到右依次搜索,找到即停:

1. -L 指定的目录

编译命令中 -L 后面跟的路径,按出现顺序从左到右搜索。

2. 环境变量 LIBRARY_PATH 指定的目录

通过设置 LIBRARY_PATH 环境变量添加的额外路径,多个路径用冒号 : 分隔。

bash 复制代码
export LIBRARY_PATH=/my/custom/lib:$LIBRARY_PATH

3. 系统默认的库目录

编译器内置的默认搜索路径,不同发行版略有差异,主要包括:

  • /usr/lib

  • /usr/local/lib

  • /lib

  • /usr/lib64(CentOS / RHEL)

  • /usr/lib/x86_64-linux-gnu/(Ubuntu / Debian)


搜索顺序总结

bash 复制代码
-L 指定目录 → LIBRARY_PATH 环境变量 → 系统默认目录

从左到右,找到就停。


4.【问题补充】

问题一:为什么平时编译 C 代码,没用过 -I-L-l 也能成功?

原因: 系统默认路径下已经存在了标准库的头文件和库文件。

  • 头文件默认路径:/usr/include

  • 库文件默认路径:/usr/lib/lib64

编译器会自动去这些路径下找,所以 #include <stdio.h> 能找到,printf 的实现也能自动链接。

补充:

C 标准库 libc 是 gcc 默认链接的,不需要手动加 -lc。这属于编译器的内置行为。


问题二:我们自己写的库,也不想用 -I-L-l,怎么办?

方法: 把自己写的头文件和库文件,分别拷贝到系统的默认路径下。

  • 头文件 → /usr/include

  • 库文件 → /usr/lib/lib64

这个过程叫做库的安装

类比:

之前学 PATH 环境变量时,把可执行程序拷贝到 PATH 包含的路径下(如 /usr/bin),就可以直接输命令运行。

库的安装是同样的道理:把头文件和库放到系统默认路径下,编译器就能自动找到。

注意:

自己写的库叫做第三方库 (不是系统库,也不是语言标准库)。

即使安装到了系统路径,编译时通常还是要加 -l 指定库名,因为系统默认只自动链接 libc,不会自动链接其他库。


问题三:为什么这里不需要指定静态链接方式(比如 -static)?

原因: 当前目录下只有静态库 libutil.a,没有对应的动态库 libutil.so

  • 当同时存在静态库和动态库时,gcc 默认优先使用动态库

  • 当只有静态库时,gcc 就只能使用静态库,不需要显式写 -static

补充:

如果想强制使用静态库(即使动态库也存在),可以加 -static 选项。

但注意:-static 会影响所有库(包括 libc),生成的可执行文件会非常大。


三.动态库制作(打包+使用)(使用makefile)

1.预备工作

动态库

  • 动态库(.so):程序在运⾏的时候才去链接动态库的代码,多个程序共享使⽤库的代码。
  • ⼀个与动态库链接的可执⾏⽂件仅仅包含它⽤到的函数⼊⼝地址的⼀个表,⽽不是外部函数所在⽬标⽂件的整个机器码
  • 在可执⾏⽂件开始运⾏以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执⾏⽂件更⼩,节省了磁盘空间。操作系统采⽤虚拟内存机制允许物理内存中的⼀份动态库被要⽤到该库的所有进程共⽤,节省了内存和磁盘空间。

这里打包动态库依旧不想把源文件暴露出去。

2.生成动态库

bash 复制代码
gcc -shared -fPIC -o libxxx.so 源文件列表
或者分步:

# 1. 编译成位置无关的目标文件
gcc -c -fPIC file1.c -o file1.o
gcc -c -fPIC file2.c -o file2.o

# 2. 打包成动态库
gcc -shared file1.o file2.o -o libxxx.so
  • shared:生成共享库格式 --生成共享库(动态库)格式
  • fPIC:产生位置无关码(position independent code)--产生位置无关码(Position Independent Code)
  • 库名规则:libxxx.so指定输出的动态库文件名
  • -o libxxx.so 指定输出的动态库文件名

命名规则

动态库命名格式:lib + 库名 + .so

库名 文件名
util libutil.so
c libc.so
m libm.so

使用时的 -l 参数:去掉 lib 前缀和 .so 后缀

bash 复制代码
gcc main.c -lutil    # 链接 libutil.so

一步完成:

gcc -shared -fPIC log.c my_math.c -o libutil.so

​​​​​​​

bash 复制代码
分步完成
gcc -c -fPIC log.c -o log.o
gcc -c -fPIC my_math.c -o my_math.o
gcc -shared log.o my_math.o -o libutil.so
bash 复制代码
 make
# 只生成静态库
make libutil.a
# 只生成动态库
make libutil.so

# 打包交付目录
make output

# 清理
make clean

在栈区和堆区中间有一个共享区,动态库的代码就是映射到这个区域。

库文件当然也是一个文件,当然也占磁盘空间。当程序运行时,需要执行库中的代码时,操作系统把库加载到内存(当然也可以局部加载,只加载当前用到的部分),然后通过页表把库映射到进程地址空间的共享区。此时代码区的指令就可以直接访问共享区中的库代码。

动态库的好处是:多个进程可以共用物理内存中的同一份库代码。每个进程通过页表把它映射到自己的共享区,这样一份库内存可以被多个进程共享,有效节省内存资源。

如果是静态链接,库代码会被拷贝到每个可执行文件的代码区。10个进程用同一个静态库,内存中就有10份重复的库代码,而实际只需要1份就够了。

动态库还有一个关键问题:库可以被加载到内存的任何位置,也可以被映射到共享区的任何区域。库中的代码地址在编译时无法确定绝对地址。比如编译时一个函数地址是 0x1234,但加载后地址变了,代码就无法执行。

解决办法是位置无关码。这里用一个例子来说明:

假设有一条队伍,排头的人叫小A,排尾的人叫小E。

  • 小A和小E之间的距离是 10 米。

  • 现在整条队伍向前移动了 5 米。

  • 小A和小E离起点的距离变了,但小A和小E之间的相对距离还是 10 米,没变。

动态库也是这样:

  • 库里的函数 A 和函数 B 之间的相对偏移是固定的。

  • 不管整个库被加载到内存的哪个位置,函数 A 和函数 B 之间的偏移量不变。

  • 所以函数 A 调用函数 B 时,只需要知道它们之间的相对距离 ,不需要知道库加载的绝对地址

这就是位置无关码 的核心思想:依赖相对偏移,不依赖绝对地址。

所以 gcc 必须用 -fPIC 选项生成位置无关码,保证库被加载到任何位置都能正确执行。


3.使用动态库

打包好库 output,李四拿到了库,李四拿到库后,写好 main.c,开始编译。

第一步:只指定头文件路径(缺库)

报错原因: 头文件找到了,但链接时找不到函数实现(没链接库)。

第二步:加上库路径(还缺库名)

报错原因: -L 只是告诉链接器"去这个目录找库",但没说具体链接哪个库,所以还是找不到实现。

第三步:加上库名

编译成功,生成 a.out


第四步:运行报错(动态库找不到)

原因:

刚才的 -I-L-l 选项是给编译器 看的,告诉编译器去哪找头文件和库。现在 ./a.out 已经是运行时 了,和 gcc 没有关系。程序运行时,操作系统需要加载动态库,但它不知道 libhello.so 在哪。


第五步:查看动态库依赖

可以看到 libhello.so => not found,系统找不到这个动态库。


为什么其他程序可以直接运行?

因为系统自带的动态库(如 libc.so)在默认路径下(如 /usr/lib/lib64),运行时系统能自动找到。

我们自己写的库是第三方库,不在默认路径,所以需要告诉系统去哪找。


解决方案(使用 LD_LIBRARY_PATH

LD_LIBRARY_PATH 是一个环境变量,用来告诉操作系统去哪里找动态库。

操作步骤:

bash 复制代码
# 使用绝对路径(推荐)
export LD_LIBRARY_PATH=/home/xyl/uselib/output/lib:$LD_LIBRARY_PATH

# 或者使用相对路径(不推荐,依赖当前目录)
export LD_LIBRARY_PATH=./output/lib:$LD_LIBRARY_PATH

验证:

复制代码

运行:

复制代码

如果指定目录下只有静态库(.a),没有动态库(.so),那么链接器会自动采用静态链接方式,将静态库的代码拷贝进可执行程序中。

如果动静态库同时存在,默认采用的是动态库。


动静态库同时存在,但非要使用静态库怎么办

答案:使用 -static 选项

-static 的作用是:摒弃默认优先使用动态库的原则,强制使用静态库进行链接。

目录下同时存在静态库(.a)和动态库(.so)时:

编译命令 链接方式 说明
gcc main.c -L./lib -lutil 动态链接(默认) 优先使用 .so
gcc main.c -L./lib -lutil -static 静态链接(强制) 只使用 .a

注意

-static 不仅影响你自己指定的库,还会影响所有库(包括 libc 等系统库)。

  • 不加 -staticlibc.so 动态链接

  • -staticlibc.a 静态链接

结果: 可执行文件体积会明显变大。

statically linked :意思是:这个程序是静态链接的。

编译方式 file 输出中的关键字
动态链接 dynamically linked
静态链接 statically linked
bash 复制代码
#查看静态程序
file static_program     # 显示 statically linked

# 查看大小(静态程序会比动态的大很多)
ls -lh static_program

# 查看动态库依赖(应该显示 not a dynamic executable)
ldd static_program

file 查看文件类型

4.运行动态库的三种方案

当程序依赖动态库(.so)时,运行时需要让系统能找到这个库。有以下三种方案:


方案一:拷贝到系统默认路径

bash 复制代码
sudo cp libutil.so /usr/lib/
sudo ldconfig

说明:

  • .so 文件拷贝到系统共享库路径(如 /usr/lib/lib64

  • 执行 ldconfig 更新库缓存

  • 系统默认会搜索这些路径,运行时自动找到

优点: 一劳永逸,所有用户和程序都能用
缺点: 需要 root 权限,可能污染系统目录


方案二:设置 LD_LIBRARY_PATH 环境变量​​​​​​​

bash 复制代码
export LD_LIBRARY_PATH=/home/ubuntu/code/lesson13/output/lib:$LD_LIBRARY_PATH
./a.out

说明:

  • 将库路径加入 LD_LIBRARY_PATH

  • 只在当前终端会话有效,重新登录后失效

优点: 简单快捷,适合测试
缺点: 临时有效,每次打开终端都要重新设置(除非写入 ~/.bashrc,但不推荐)


方案三:配置 /etc/ld.so.conf.d/(永久生效)

bash 复制代码
# 1. 创建一个配置文件
echo "/home/ubuntu/code/lesson13/output/lib" | sudo tee /etc/ld.so.conf.d/libutil.conf

# 2. 更新库缓存
sudo ldconfig

# 3. 验证
ldconfig -p | grep libutil

说明:

  • /etc/ld.so.conf.d/ 下创建一个 .conf 文件

  • 文件内容就是库的绝对路径

  • 执行 ldconfig 后永久生效

优点: 永久生效,不需要每次设置
缺点: 需要 root 权限;可能污染系统环境(自己写的库不建议这样做)


三种方案对比

方案 永久性 权限 适用场景
拷贝到 /usr/lib 永久 root 正式安装、系统级库
LD_LIBRARY_PATH 临时 普通用户 开发、测试
/etc/ld.so.conf.d/ 永久 root 第三方开源库、正式部署

5.使用外部库

系统中其实有很多库,它们通常由一组互相关联的、用来完成某项常见工作的函数构成。

例如:

  • libm.so:数学库,提供 sin()cos()sqrt() 等数学函数

  • libpthread.so:线程库,提供多线程相关函数

  • libc.so:C 标准库(默认链接,不需要手动指定)

  • libncurses.so:屏幕界面处理库,用于文本界面程序


6.如何链接外部库

使用 -l 选项指定要链接的库:

bash 复制代码
gcc main.c -lm          # 链接数学库
gcc main.c -lpthread    # 链接线程库
gcc main.c -lncurses    # 链接 ncurses 库
gcc main.c -lm -lpthread # 链接多个库

推荐⼀篇不错的使⽤指南:https://blog.csdn.net/bdn_nbd/article/details/134019142


7.库文件名与引入名的对应关系

库文件名 去掉 lib 前缀 去掉 .so / .a 后缀 -l 参数
libc.so c.so c -lc
libm.so m.so m -lm
libpthread.so pthread.so pthread -lpthread
libncurses.so ncurses.so ncurses -lncurses
libutil.so util.so util -lutil

规则: -l 后面的名字 = 去掉 lib 前缀 + 去掉 .so.a 后缀

相关推荐
j_xxx404_12 小时前
Linux线程:核心机制与优雅的 C++ 封装实践|附源码
linux·运维·服务器·开发语言·c++·人工智能·ai
IMPYLH12 小时前
Linux 的 users 命令
linux·运维·服务器·前端·数据库·bash
xiaoye-duck12 小时前
【Linux:文件】Linux 动静态库详解:动态链接与动态库加载深度解析
linux
加油201912 小时前
嵌入式软件技术栈和学习路线详解
linux·arm开发·数据结构·mqtt·设计模式·嵌入式
Oj92q85H512 小时前
如何在Dev-C++中使用TDM-GCC编译项目
linux·开发语言·c++
行走的大喇叭12 小时前
计算机系统组成及常见概念
linux·运维·计算机网络
kyle~12 小时前
ROS2---rosbag2记录和回放话题、服务和动作数据
linux·机器人·数据采集·ros2
j_xxx404_12 小时前
Linux线程控制:从用户态控制到内核级克隆全链路解析
linux·运维·服务器·开发语言·c++·ai
z2005093012 小时前
【linux学习】进程的概念和在linux系统下的基本实现情况01
linux·网络·学习