LINUX系统-19-库制作与原理(一)

LINUX系统之动静态库制作

一、什么是库

库是写好的、已经存在的、成熟的可以直接利用的代码。

什么意思呢?

库是写好的、成熟的、可以直接复用的代码集合。比如我想用 vector,只需要 #include ,编译器就会利用模板特性,在编译时自动帮我生成 vector 的底层实现代码。这让我无需重新造轮子,就能直接使用强大的数据结构。

其本质上说库其实是一种可执行代码的二进制形式,可以被OS载入内存执行,库有2中形式:

1、静态库,在linux中以.a结尾,在windows中以.lib结尾。

2、动态库,在linux中以.so结尾,在windows中以.dll结尾。

1、回顾代码编译为可执行文件的过程

.c文件在经过编译的过程后生成.o 文件,所有的.o文件被链接起来,生成可执行文件 ELF,即所谓的.exe文件

我们创建3个文件

c 复制代码
// math.h
#ifndef MATH_H
#define MATH_H

int add(int x,int y);

#endif
c 复制代码
// math.c
#include "math.h"
int add(int a, int b) {
    return a + b;
}
c 复制代码
// main.c
#include <stdio.h>
#include "math.h"

int main() 
{
    printf("1 + 2 = %d\n", add(1, 2));
    return 0;
}

我们逐一生成-o文件,并重新链接生成可执行文件。

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ tree
.
├── main.c
├── makefile
├── math.c
└── math.h

1 directory, 4 files
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c math.c -o math.o # 生成math.c文件对应的.o文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c main.c -o main.o # 生成main.c文件对应的.o文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc math.o main.o -o main # 将2个.o文件链接为ELF可执行文件

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ tree
.
├── main
├── main.c
├── main.o
├── makefile
├── math.c
├── math.h
└── math.o

1 directory, 7 files
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ./main # 执行可执行文件
1 + 2 = 3

2、动静态库

静态库的使用上类似于编写代码时的 include,我们在编写程序的时候,直接将.a中的数据直接拷贝进可执行文件中。

ubuntu中的动静态库

C语言静态库

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ls -l /lib/x86_64-linux-gnu/libc.a
-rw-r--r-- 1 root root 6135280 Jan 30 16:27 /lib/x86_64-linux-gnu/libc.a

C语言动态库

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ls -l /lib/x86_64-linux-gnu/libc*.so
-rw-r--r-- 1 root root 283 Jan 30 16:27 /lib/x86_64-linux-gnu/libc.so

C++静态库

bash 复制代码
(base) benjiang@BENJIANGLIU:/usr/lib/gcc/x86_64-linux-gnu/13$ ls -l libstdc++*
-rw-r--r-- 1 root root 6583910 Dec 19  2025 libstdc++.a # 静态库文件
lrwxrwxrwx 1 root root      40 Dec 19  2025 libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6 #动态库的软连接

C++动态库

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ls -l /lib/x86_64-linux-gnu/libstdc*
lrwxrwxrwx 1 root root      19 Dec 19  2025 /lib/x86_64-linux-gnu/libstdc++.so.6 -> libstdc++.so.6.0.33 # C++ 动态库
-rw-r--r-- 1 root root 2592224 Dec 19  2025 /lib/x86_64-linux-gnu/libstdc++.so.6.0.33 # C++ 动态库

为什么C和C的动静态库不是放在一起的?

C 语言库 (libc):属于 libc6 或 libc6-dev 包。这是整个 Linux 系统的绝对基石。所有的系统调用、基础字符串操作等都依赖它。因此,它被安装在最核心的系统库目录 /lib/x86_64-linux-gnu/ 下。

C++ 库 (libstdc++):属于 libstdc++6 或 libstdc+±13-dev 包。它是 GCC 编译器套件的一部分。虽然它也很重要,但它本质上是编译器运行库,而不是操作系统内核直接依赖的基础库。

3、动静态库编译过程

例如我们写好的

c 复制代码
// 01_文件描述符分配规则.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main()
{
    //close(0); // 关闭stdin,标准输入
    close(2); // 关闭stderr,标准错误
	int fd = open("myfile.txt",O_RDONLY);
	if(fd < 0)
	{
		perror("open:");
		return -1;
	}

	printf("fd:%d \n",fd);
	close(fd);
	return 0;
}

1、默认gcc使用动态连接:

我们把上面这段代码编译生成可执行文件,然后使用ldd命令查看代码的依赖库。

bash 复制代码
(base) benjiang@BENJIANGLIU:~/C/02_文件描述符fd$ ldd 01_文件描述符分配规则
        linux-vdso.so.1 (0x00007ffe99237000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007db3ad600000) // 库文件
        /lib64/ld-linux-x86-64.so.2 (0x00007db3ad9b0000)                  // 加载器

ldd (List Dynamic Dependencies) 命令用于打印一个可执行文件在运行时需要依赖的动态链接库(共享库)。

上面ldd的结果可以看出,02_文件描述符fd$ ldd 01_文件描述符分配规则 这个可执行文件需要以下三个组件:

1、linux-vdso.so.1 (0x00007ffe99237000)

含义:这是 vDSO (virtual dynamic shared object),即虚拟动态共享对象。

作用:它不是一个存在于硬盘上的真实文件,而是 Linux 内核在程序启动时,直接映射到用户空间内存中的一小段代码。

它的主要作用是让用户态程序能够高效地调用某些内核系统调用(例如获取当前时间 gettimeofday),从而避免陷入内核态的开销(上下文切换)。括号中的地址是它被加载到内存中的虚拟地址。

2、libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007db3ad600000)

含义:这是 GNU C 库 (glibc),是 Linux 系统中最核心的基础库。

作用:代码中调用的 printf、close、open、perror 等函数,实际上都是通过这个库来封装并调用底层系统调用的。

格式解析:

libc.so.6:库的名称。

=>:表示该库在磁盘上的实际路径。

/lib/x86_64-linux-gnu/libc.so.6:该库在你系统中的绝对物理路径。

(0x00007db3ad600000):程序运行时,该库被加载到内存中的起始地址。

3、 /lib64/ld-linux-x86-64.so.2 (0x00007db3ad9b0000)

含义:这是 Linux 系统的动态链接器/加载器 (Dynamic Linker/Loader)。

作用:它是程序运行的"大管家"。当你在终端输入命令执行程序时,内核首先会启动这个加载器。

它负责读取可执行文件的 ELF 头部,找到程序依赖的其他动态库(如上面的 libc.so.6),将它们加载到内存中,完成符号解析和重定位,最后将控制权交给程序的 main 函数。

ldd 的输出告诉你,你的这个 C 语言程序并不是完全独立运行的,它依赖于系统的 C 语言基础库 (libc),并且需要系统的动态加载器 (ld-linux) 来在启动时帮你把这些库拼装好,同时内核还为你提供了一套高效的内存级系统调用接口 (vDSO)。

bash 复制代码
(base)  benjiang@BENJIANGLIU:~/C/02_文件描述符fd$ file 01_文件描述符分配规则
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=345bb9be1dc7312f7fd397a29e1595f2d6d0fe85, for GNU/Linux 3.2.0, not stripped

发现这个可执行文件是基于动态链接的

2、使用-static 使gcc强制静态链接

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ gcc static_link.c -o static_link -static
# 还是上面那段程序,我们使用gcc xx.c -o xx -static 使程序生成静态链接程序

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ll
total 856
drwxr-xr-x  2 benjiang benjiang   4096 Jul  6 20:09 ./
drwxr-xr-x 24 benjiang benjiang   4096 Jul  6 16:57 ../
-rw-r--r--  1 benjiang benjiang   1372 Jul  6 19:08 libmymath.a
-rwxr-xr-x  1 benjiang benjiang  16024 Jul  6 19:09 main*
-rw-r--r--  1 benjiang benjiang    106 Jul  6 18:24 main.c
-rw-r--r--  1 benjiang benjiang   1520 Jul  6 19:39 makefile
-rw-r--r--  1 benjiang benjiang     61 Jul  6 18:24 math.c
-rw-r--r--  1 benjiang benjiang     60 Jul  6 18:23 math.h
-rw-r--r--  1 benjiang benjiang   1232 Jul  6 18:55 math.o
-rwxr-xr-x  1 benjiang benjiang 820520 Jul  6 20:09 static_link* // 发现生成静态链接的可执行程序
-rw-r--r--  1 benjiang benjiang    382 Jul  6 20:08 static_link.c

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ file static_link // 再次打印可执行文件的格式
static_link: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked,BuildID[sha1]=8cb8d12413f633ab1174ff146cb42b14aee9dfd8, for GNU/Linux 3.2.0, not stripped

3、比较静态链接和动态链接

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ll
total 836
drwxr-xr-x  3 benjiang benjiang   4096 Jul  6 21:45 ./
drwxr-xr-x 24 benjiang benjiang   4096 Jul  6 16:57 ../
-rwxr-xr-x  1 benjiang benjiang  16096 Jul  6 21:45 dynamic_link*
drwxr-xr-x  2 benjiang benjiang   4096 Jul  6 21:34 math/
-rwxr-xr-x  1 benjiang benjiang 820520 Jul  6 20:09 static_link*
-rw-r--r--  1 benjiang benjiang    382 Jul  6 20:08 static_link.c

同样一个static_link.c文件,使用静态链接生成的可执行文件大小为820kb,而动态链接的可执行文件大小为16KB

  1. 为什么静态链接(820KB)这么大?
    当你使用静态链接时,链接器会把你程序调用的所有外部代码(包括整个 C 标准库)全部"抠"出来,硬塞进你的 ELF 文件中。
    C 标准库的体积:C 标准库(glibc)包含了成千上万个函数(printf, malloc, fopen 等)。即使你的 static_link.c 里只用到了 printf,但由于标准库内部的函数调用关系极其复杂,链接器为了保证不出错,会把整个标准库的绝大部分模块都复制进去。
    结果:你的 static_link 可执行文件变成了一个"自给自足"的胖子,里面包含了完整的 C 运行时环境。好处是:把它拷贝到任何一台同架构的 Linux 机器上,它都能直接运行,不依赖任何外部环境。
  2. 为什么动态链接(16KB)这么小?
    当你使用动态链接时,链接器在编译阶段不会把 C 标准库的机器码复制进你的 ELF 文件中。
    只留"提货券":链接器只会在你的 ELF 文件中写入一个"动态符号表(Dynamic Symbol Table)"和"重定位信息"。这相当于在你的程序里贴了一张张"提货券"(比如写着:"这里需要用到 printf,请帮我找一下")。
    结果:你的 dynamic_link 文件非常小巧,只有你自己写的代码和这些"提货券"。当程序真正运行时,Linux 的动态链接器(ld-linux.so)会介入,在内存中把系统共享的 libc.so 库加载进来,然后把你的"提货券"替换成真实的内存地址。

4、如何制作静态库

1、制作静态库.a文件

我们在第一个例子上增加若干个数学实现。

c 复制代码
// add.h
#ifndef ADD_H
#define ADD_H

int add(int x,int y);

#endif
c 复制代码
add
#include "math.h"
int add(int a, int b) {
    return a + b;
}
c 复制代码
// sub.h
#ifndef ADD_H
#define ADD_H

int sub(int x,int y);

#endif
c 复制代码
// sub.c
#include "sub.h"
int sub(int a, int b) {
    return a - b;
}
c 复制代码
// mul.h
#ifndef ADD_H
#define ADD_H

int mul(int x,int y);

#endif
c 复制代码
// mul.c
#include "mul.h"
int mul(int a, int b) {
    return a * b;
}
c 复制代码
// main.c
#include <stdio.h>
#include "add.h"
#include "sub.h"
#include "mul.h"

int main() 
{
    printf("1 + 2 = %d\n", add(1, 2));
    printf("1 - 2 = %d\n", sub(1, 2));
    printf("1 * 2 = %d\n", mul(1, 2));
    return 0;
}

现在我们得到了3个.c文件,3个.h文件,用于实现加减乘功能,以及一个main.c文件实现各个功能

查看所有文件

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ tree 
.
├── add.c
├── add.h
├── main.c
├── mul.c
├── mul.h
├── sub.c
└── sub.h

# 第一步将所有的.c文件编译生成.o文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c add.c -o add.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c sub.c -o sub.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c mul.c -o mul.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c main.c -o main.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ tree 
.
├── add.c
├── add.h
├── add.o
├── main.c
├── main.o
├── makefile
├── mul.c
├── mul.h
├── mul.o
├── myapp
├── sub.c
├── sub.h
└── sub.o


# 第二步将所有的.o文件链接为可执行文件ELF
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc main.o add.o sub.o mul.o -o myapp

# 第三步 执行可执行文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ./myapp 
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2

这样我们就实现了编译和链接生成可执行文件的过程。

如果有函数反复调用这些功能,我们就需要反复编译连接这些.o文件。

假如我可以将所有的功能性函数归档成为一个静态库,那么我们就可以将静态库和任意的main.o文件进行链接,而不需要链接add.o、sub.o、mul.o这些文件了。

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ar -rc libmymath.a add.o sub.o mul.o
# r (replace):如果库中已经存在同名的 .o 文件,就替换它;如果不存在,就插入到库中。
# c (create):如果指定的库文件(libmymath.a)不存在,则创建一个新库。

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ll
total 76
drwxr-xr-x 2 benjiang benjiang  4096 Jul  6 21:08 ./
drwxr-xr-x 3 benjiang benjiang  4096 Jul  6 20:30 ../
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 20:56 add.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 20:28 add.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:02 add.o
-rw-r--r-- 1 benjiang benjiang  3972 Jul  6 21:08 libmymath.a # 生成的静态库文件
-rw-r--r-- 1 benjiang benjiang   217 Jul  6 20:38 main.c
-rw-r--r-- 1 benjiang benjiang  1864 Jul  6 21:02 main.o
-rw-r--r-- 1 benjiang benjiang  1533 Jul  6 20:35 makefile
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 20:31 mul.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 20:31 mul.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:02 mul.o
-rwxr-xr-x 1 benjiang benjiang 16136 Jul  6 21:03 myapp*
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 20:30 sub.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 20:29 sub.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:02 sub.o

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ rm -rf myapp # 删除之前的ELF文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc main.o libmymath.a -o myapp # 使用静态链接库进行链接
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ./myapp # 执行ELF文件
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2

在本例子中,我们使用了:gcc main.o libmymath.a -o myapp 生成可执行文件ELF

但是在大型项目中,因为库文件可能分布在若干了位置,我们使用-L 和-l 让编译器自己到指定的目录去寻找库文件。

bash 复制代码
# 目标规则:生成静态库 libmymath.a,它依赖于 math.o 文件
# $@ = 目标文件 (Target)
# $^ = 所有依赖文件 (All Dependencies)
# $< = 第一个依赖文件 (First Dependency)

libmymath.a: add.o sub.o mul.o
# 使用 ar 工具将目标文件打包成静态库
# -r (replace): 如果库中已有同名文件则替换,没有则插入
# -c (create): 如果库文件不存在则创建,且不给出警告
# $@: 自动变量,代表目标文件(即 libmymath.a)
# $^: 自动变量,代表所有的依赖文件(即 math.o)
	@ar -rc $@ $^
	
# 打印构建完成的提示信息
# @: 表示执行该命令时,不在终端显示这条命令本身,只显示输出结果
# $^ 和 $@: 同上,分别代表依赖文件和目标文件
	@echo "build $^ to $@ ... done"

# 模式规则(通配符规则):将任意 .c 文件编译为同名的 .o 文件
# %.o: 代表任意目标文件(如 math.o)
# %.c: 代表任意依赖文件(如 math.c)
%.o: %.c
# 使用 gcc 编译器进行编译(只编译不链接)
# -c: 表示只编译源文件生成目标文件(.o),不进行链接
# $<: 自动变量,代表第一个依赖文件(即对应的 .c 文件)
	@gcc -c $<  # 将所有的.c转换为.o
	
# 打印编译完成的提示信息
	@echo "compiling $< to $@ ... done"
.PHONY:clean
clean:
	@rm -rf *.a *.o stdc*
	@echo "clean ... done"

.PHONY:output
output:
	@mkdir -p stdc/include 
	@mkdir -p stdc/lib
	@cp -f *.h stdc/include
	@cp -f *.a stdc/lib
	@echo "output stdc ... done"

让我们执行makefile

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ make
compiling add.c to add.o ... done
compiling sub.c to sub.o ... done
compiling mul.c to mul.o ... done
build add.o sub.o mul.o to libmymath.a ... done

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ll
total 56
drwxr-xr-x 2 benjiang benjiang 4096 Jul  6 21:33 ./
drwxr-xr-x 3 benjiang benjiang 4096 Jul  6 20:30 ../
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 20:56 add.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 20:28 add.h
-rw-r--r-- 1 benjiang benjiang 1232 Jul  6 21:33 add.o
-rw-r--r-- 1 benjiang benjiang 3972 Jul  6 21:33 libmymath.a
-rw-r--r-- 1 benjiang benjiang  217 Jul  6 20:38 main.c
-rw-r--r-- 1 benjiang benjiang 1520 Jul  6 21:33 makefile
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 20:31 mul.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 20:31 mul.h
-rw-r--r-- 1 benjiang benjiang 1232 Jul  6 21:33 mul.o
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 20:30 sub.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 20:29 sub.h
-rw-r--r-- 1 benjiang benjiang 1232 Jul  6 21:33 sub.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc -c main.c -o main.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ gcc main.o -L. -lmymath -o myapp
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ./myapp
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ar -t libmymath.a
add.o
sub.o
mul.o # 发现.a文件中归档了3个.o文件
# -t 列出所有的静态库中的文件
# verbose 详细信息

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math$ ll
total 76
-rwxr-xr-x 1 benjiang benjiang 16136 Jul  6 21:34 myapp* # myapp的大小是16KB

总结:

我们使用编译和归档2个过程去实现库文件的制作:编译 gcc -c 生成.o文件,归档 ar -rc 命令实现.a制作。
最终使用 gcc 主函数.o -L库文件路径 -l库文件名 -o ELF文件名 ,将库文件与主函数.o文件一起链接生成最终的可执行文件。

在最后的链接阶段,链接器并不会把整个 libmymath.a 塞进 myapp 里,而是只把 main.o 实际调用到的 add.o、sub.o、mul.o 的机器码"抠"出来,和 main.o 拼在一起,最终生成了那个可以直接运行的 ELF 文件。

2、制作动态库文件.so

动态库其本质是在程序运行的时候才会连接动态库的代码,很多程序共享使用库的代码。

一个与动态库链接的可执行程序仅仅包含一个函数入口的地址表,而不是外部函数所在的目标文件整个机器码。

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中赋值到内存中,这个过程成为动态链接。

动态库可以在多个程序之间共享,所以动态链接使得可执行文件更小,节省磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态链接库被要用到的所有进程共享,节省了内存和磁盘空间。

bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ mv  math math_static # 把之前的math改名为静态链接目录
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ll
total 836
drwxr-xr-x  3 benjiang benjiang   4096 Jul  6 21:58 ./
drwxr-xr-x 24 benjiang benjiang   4096 Jul  6 16:57 ../
-rwxr-xr-x  1 benjiang benjiang  16096 Jul  6 21:45 dynamic_link*
drwxr-xr-x  2 benjiang benjiang   4096 Jul  6 21:34 math_static/
-rwxr-xr-x  1 benjiang benjiang 820520 Jul  6 20:09 static_link*
-rw-r--r--  1 benjiang benjiang    382 Jul  6 20:08 static_link.c

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ cp -rf math_static/ math_dynamic # 拷贝一份并重命名为动态链接目录
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ ll
total 840
drwxr-xr-x  4 benjiang benjiang   4096 Jul  6 21:59 ./
drwxr-xr-x 24 benjiang benjiang   4096 Jul  6 16:57 ../
-rwxr-xr-x  1 benjiang benjiang  16096 Jul  6 21:45 dynamic_link*
drwxr-xr-x  2 benjiang benjiang   4096 Jul  6 21:59 math_dynamic/
drwxr-xr-x  2 benjiang benjiang   4096 Jul  6 21:34 math_static/
-rwxr-xr-x  1 benjiang benjiang 820520 Jul  6 20:09 static_link*
-rw-r--r--  1 benjiang benjiang    382 Jul  6 20:08 static_link.c
bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26$ cd math_dynamic/
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ll
total 76
drwxr-xr-x 2 benjiang benjiang  4096 Jul  6 21:59 ./
drwxr-xr-x 4 benjiang benjiang  4096 Jul  6 21:59 ../
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 add.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 add.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:59 add.o
-rw-r--r-- 1 benjiang benjiang  3972 Jul  6 21:59 libmymath.a
-rw-r--r-- 1 benjiang benjiang   217 Jul  6 21:59 main.c
-rw-r--r-- 1 benjiang benjiang  1864 Jul  6 21:59 main.o
-rw-r--r-- 1 benjiang benjiang  1520 Jul  6 21:59 makefile
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 mul.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 mul.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:59 mul.o
-rwxr-xr-x 1 benjiang benjiang 16136 Jul  6 21:59 myapp*
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 sub.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 sub.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 21:59 sub.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ make clean # 删除所有的.a\.o文件
clean ... done
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ll
total 56
drwxr-xr-x 2 benjiang benjiang  4096 Jul  6 21:59 ./
drwxr-xr-x 4 benjiang benjiang  4096 Jul  6 21:59 ../
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 add.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 add.h
-rw-r--r-- 1 benjiang benjiang   217 Jul  6 21:59 main.c
-rw-r--r-- 1 benjiang benjiang  1520 Jul  6 21:59 makefile
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 mul.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 mul.h
-rwxr-xr-x 1 benjiang benjiang 16136 Jul  6 21:59 myapp*
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 sub.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 sub.h
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ rm -rf myapp # 删除可执行文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ll
total 40
drwxr-xr-x 2 benjiang benjiang 4096 Jul  6 21:59 ./
drwxr-xr-x 4 benjiang benjiang 4096 Jul  6 21:59 ../
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 21:59 add.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 21:59 add.h
-rw-r--r-- 1 benjiang benjiang  217 Jul  6 21:59 main.c
-rw-r--r-- 1 benjiang benjiang 1520 Jul  6 21:59 makefile
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 21:59 mul.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 21:59 mul.h
-rw-r--r-- 1 benjiang benjiang   60 Jul  6 21:59 sub.c
-rw-r--r-- 1 benjiang benjiang   58 Jul  6 21:59 sub.h

编译 链接过程

bash 复制代码
# 编译过程
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc -c add.c -o add.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc -c sub.c -o sub.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc -c mul.c -o mul.o

# 链接过程
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc -o libmymath.so add.o sub.o mul.o -shared
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ll
total 68
drwxr-xr-x 2 benjiang benjiang  4096 Jul  6 22:03 ./
drwxr-xr-x 4 benjiang benjiang  4096 Jul  6 21:59 ../
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 add.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 add.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:02 add.o
-rwxr-xr-x 1 benjiang benjiang 15224 Jul  6 22:03 libmymath.so* # 生成动态链接库
-rw-r--r-- 1 benjiang benjiang   217 Jul  6 21:59 main.c
-rw-r--r-- 1 benjiang benjiang  1520 Jul  6 21:59 makefile
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 mul.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 mul.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:02 mul.o
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 sub.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 sub.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:02 sub.o

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc -c main.c -o main.o # 编译mian.o文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc main.o -L. -lmymath -o myapp # 使用动态链接链接main.o文件
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ./myapp # 发现报错,找不到贡献库文件.so
./myapp: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ldd myapp 
        linux-vdso.so.1 (0x00007ffe73b67000)
        libmymath.so => not found # 发现.so贡献库文件找不到
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b5815c00000)
        /lib64/ld-linux-x86-64.so.2 (0x00007b5815e42000)
        
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ gcc main.o -L. -lmymath -Wl,-rpath=. -o myapp
# 在编译生成可执行文件时,使用 -Wl,-rpath 参数,直接把动态库的路径"写死"在 ELF 可执行文件里。这样以后运行就不需要再设置环境变量了
./myapp
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ./myapp 
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ 

既然我们已经知道了动态链接库的制作过程,我们就可以方便的可makefile文件了

bash 复制代码
# 目标规则:生成静态库 libmymath.a,它依赖于 math.o 文件
# $@ = 目标文件 (Target)
# $^ = 所有依赖文件 (All Dependencies)
# $< = 第一个依赖文件 (First Dependency)
myapp:main.o libmymath.so
	$gcc main.o -L. -lmymath -Wl,-rpath=. -o myapp
	$gcc -c main.c -o main.o

libmymath.so: add.o sub.o mul.o
# 使用gcc -o xxx xxx -shared生成贡献库文件
# $@: 自动变量,代表目标文件(即 libmymath.so)
# $^: 自动变量,代表所有的依赖文件(即 math.o)
	@gcc -o $@ $^ -shared 

# 打印构建完成的提示信息
# @: 表示执行该命令时,不在终端显示这条命令本身,只显示输出结果
# $^ 和 $@: 同上,分别代表依赖文件和目标文件
	@echo "build $^ to $@ ... done"

# 模式规则(通配符规则):将任意 .c 文件编译为同名的 .o 文件
# %.o: 代表任意目标文件(如 math.o)
# %.c: 代表任意依赖文件(如 math.c)
%.o: %.c
# 使用 gcc 编译器进行编译(只编译不链接)
# -c: 表示只编译源文件生成目标文件(.o),不进行链接
# $<: 自动变量,代表第一个依赖文件(即对应的 .c 文件)
	@gcc -fPIC -c $<  # 将所有的.c转换为.o
# 生成一段不依赖于绝对内存地址的机器码。
# 这意味着编译出来的代码可以被加载到内存中的任何位置,而不会因为加载地址的改变而产生错误。

# 打印编译完成的提示信息
	@echo "compiling $< to $@ ... done"
	
.PHONY:clean
clean:
	@rm -rf *.a *.o stdc*
	@echo "clean ... done"

.PHONY:output
output:
	@mkdir -p stdc/include 
	@mkdir -p stdc/lib
	@cp -f *.h stdc/include
	@cp -f *.a stdc/lib
	@echo "output stdc ... done"
bash 复制代码
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ make clean
clean ... done
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ make
compiling main.c to main.o ... done
compiling add.c to add.o ... done
compiling sub.c to sub.o ... done
compiling mul.c to mul.o ... done
build add.o sub.o mul.o to libmymath.so ... done
cc main.o -L. -lmymath -Wl,-rpath=. -o myapp
cc -c main.c -o main.o

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ll
total 88
drwxr-xr-x 2 benjiang benjiang  4096 Jul  6 22:19 ./
drwxr-xr-x 4 benjiang benjiang  4096 Jul  6 21:59 ../
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 add.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 add.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:19 add.o
-rwxr-xr-x 1 benjiang benjiang 15224 Jul  6 22:19 libmymath.so*
-rw-r--r-- 1 benjiang benjiang   217 Jul  6 21:59 main.c
-rw-r--r-- 1 benjiang benjiang  1864 Jul  6 22:19 main.o
-rw-r--r-- 1 benjiang benjiang  1493 Jul  6 22:20 makefile
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 mul.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 mul.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:19 mul.o
-rwxr-xr-x 1 benjiang benjiang 16048 Jul  6 22:19 myapp*
-rw-r--r-- 1 benjiang benjiang    60 Jul  6 21:59 sub.c
-rw-r--r-- 1 benjiang benjiang    58 Jul  6 21:59 sub.h
-rw-r--r-- 1 benjiang benjiang  1232 Jul  6 22:19 sub.o
(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$ ./myapp 
1 + 2 = 3
1 - 2 = -1
1 * 2 = 2

(base) benjiang@BENJIANGLIU:~/Linux/src/lesson26/math_dynamic$  file libmymath.so 
libmymath.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=da9b2e98290b1820976d3b33c32be15672749355, not stripped

file检查.so文件的类型,发现其本质也是一个ELF文件

相关推荐
瓦学妹2 小时前
2026亚马逊本土店怎么注册?入驻流程、资料准备与常见问题
大数据·运维·人工智能
FriendshipT3 小时前
Ubuntu 20.04 下使用 Ollama 本地部署 AI 大模型
linux·人工智能·python·深度学习·ubuntu
Ivan CloudBay3 小时前
网站为什么会使用缓存?
服务器·网络·缓存·云服务器
bellus-3 小时前
win11使用ubuntu 26.04
linux·运维·ubuntu
复园电子4 小时前
USB Over IP技术详解:基于USB服务器实现USB设备远程访问、重定向与集中管理
服务器·网络协议·tcp/ip
TlSfoward4 小时前
TLSFoward 能帮你看到什么 TLSFOWARD抓包工具
服务器·爬虫·网络协议·https
略略略咯咯4 小时前
centos更换镜像源
linux·运维·centos
j7~4 小时前
【Linux】二十七.线程篇四《Linux多线程编程:线程互斥(互斥量的底层到封装)、线程安全和冲入、死锁》---详解
linux·开发语言·c++·线程安全·死锁·线程互斥·重入
IKUN家族5 小时前
Spring MVC(三)
运维·服务器·spring boot·spring·servlet·java-ee