目录
[二、静态库的创建流程(完整步骤 + 命令解析)](#二、静态库的创建流程(完整步骤 + 命令解析))
[三、静态库的使用流程(测试程序编译 + 运行)](#三、静态库的使用流程(测试程序编译 + 运行))
[四、Makefile 的作用(自动化编译)](#四、Makefile 的作用(自动化编译))
二、动态库的完整创建流程(实操对应myLog/myPrint模块)
三、动态库的使用流程(结合静态库一起链接,实操testlib目录)
[四、Makefile 的自动化逻辑(动态 + 静态库一体化构建)](#四、Makefile 的自动化逻辑(动态 + 静态库一体化构建))
[一、为什么动态库必须用 -fPIC?从动态库的加载困境说起](#一、为什么动态库必须用 -fPIC?从动态库的加载困境说起)
[二、-fPIC(Position Independent Code):位置无关码的核心原理](#二、-fPIC(Position Independent Code):位置无关码的核心原理)
[三、静态库为什么不需要 -fPIC?](#三、静态库为什么不需要 -fPIC?)
[四、-fPIC 的核心价值](#四、-fPIC 的核心价值)
设计一个静态库
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ ll
total 16
-rw-rw-r-- 1 ranjiaju ranjiaju 228 Jan 30 10:53 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 282 Jan 30 09:42 mymath.c
-rw-rw-r-- 1 ranjiaju ranjiaju 139 Jan 30 09:43 mymath.h
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Feb 5 10:08 testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat mymath.h
#pragma once
#include<stdio.h>
int errorno;
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat mymath.c
#include"mymath.h"
int errorno = 0;
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
if(y == 0)
{
errorno = 1;
return -1;
}
return x / y;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat makefile
lib=libmymath.a
$(lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
.PHONY:clean
clean:
rm -rf *.o *.a lib
.PHONY:output
output:
mkdir -p lib/include
mkdir -p lib/mymathlib
cp *.h lib/include
cp *.a lib/mymathlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ make
gcc -c mymath.c
ar -rc libmymath.a mymath.o
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ ll
total 24
-rw-rw-r-- 1 ranjiaju ranjiaju 1880 Feb 5 11:20 libmymath.a
-rw-rw-r-- 1 ranjiaju ranjiaju 228 Jan 30 10:53 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 282 Jan 30 09:42 mymath.c
-rw-rw-r-- 1 ranjiaju ranjiaju 139 Jan 30 09:43 mymath.h
-rw-rw-r-- 1 ranjiaju ranjiaju 1704 Feb 5 11:20 mymath.o
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Feb 5 10:08 testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ make output
mkdir -p lib/include
mkdir -p lib/mymathlib
cp *.h lib/include
cp *.a lib/mymathlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ ll
total 28
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 5 11:20 lib
-rw-rw-r-- 1 ranjiaju ranjiaju 1880 Feb 5 11:20 libmymath.a
-rw-rw-r-- 1 ranjiaju ranjiaju 228 Jan 30 10:53 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 282 Jan 30 09:42 mymath.c
-rw-rw-r-- 1 ranjiaju ranjiaju 139 Jan 30 09:43 mymath.h
-rw-rw-r-- 1 ranjiaju ranjiaju 1704 Feb 5 11:20 mymath.o
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Feb 5 10:08 testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cp -r ./lib testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cd testlib/
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ll
total 12
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 5 11:20 lib
-rw-rw-r-- 1 ranjiaju ranjiaju 126 Feb 5 10:08 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 106 Jan 30 12:24 test.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ cat test.c
#include"mymath.h"
int main()
{
printf("1+1=%d, errorno=%d\n", add(1, 1), errorno);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ cat makefile
test.out:test.c
gcc test.c -o test.out -I ./lib/include -L ./lib/mymathlib -lmymath
.PHONY:clean
clean:
rm -rf test.out lib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ make
gcc test.c -o test.out -I ./lib/include -L ./lib/mymathlib -lmymath
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ll
total 24
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 5 11:20 lib
-rw-rw-r-- 1 ranjiaju ranjiaju 126 Feb 5 10:08 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 106 Jan 30 12:24 test.c
-rwxrwxr-x 1 ranjiaju ranjiaju 8616 Feb 5 11:22 test.out
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ./test.out
1+1=2, errorno=0
一、静态库的核心概念
静态库(后缀为.a)是 Linux 下目标文件(.o)的归档文件 ,编译时会将库中代码完整复制到可执行文件中,生成的可执行文件运行时不依赖原静态库文件 ,具备独立性。静态库命名需遵循lib[库名].a的规则(如示例中libmymath.a)。
二、静态库的创建流程(完整步骤 + 命令解析)
步骤 1:编写静态库的源码文件
需准备头文件(.h) 和实现文件(.c):
mymath.h:声明库对外提供的接口(函数、全局变量),如add/sub/mul/div函数声明、errorno全局变量声明,供使用者包含;mymath.c:实现头文件声明的函数,定义全局变量(如errorno = 0),是静态库的核心逻辑载体。
步骤 2:编译生成目标文件(.o)
命令 :gcc -c mymath.c
- 参数
-c:表示 "只编译不链接",仅将.c文件转换为二进制目标文件mymath.o(包含函数的二进制指令,但未完成符号解析和链接); - 输出:生成
mymath.o文件,是构建静态库的基础单元。
步骤 3:将目标文件归档为静态库(.a)
命令 :ar -rc libmymath.a mymath.o
ar:Linux 归档工具,用于将单个 / 多个目标文件打包为静态库;- 参数
-r:若库文件已存在,替换库中同名目标文件;若库不存在,创建库文件; - 参数
-c:强制创建库文件(即使不存在); - 输出:生成静态库
libmymath.a,包含mymath.o的所有二进制代码。
步骤 4:规范整理静态库(可选但推荐)
通过make output自动化整理发布目录,便于使用者调用:命令 :make output
mkdir -p lib/include:创建头文件存放目录(-p确保目录不存在时创建);mkdir -p lib/mymathlib:创建库文件存放目录;cp *.h lib/include:将头文件复制到include目录,供使用者指定头文件路径;cp *.a lib/mymathlib:将静态库复制到mymathlib目录,供使用者指定库文件路径。
三、静态库的使用流程(测试程序编译 + 运行)
步骤 1:准备测试程序
编写test.c:包含静态库头文件mymath.h,调用库中函数(如add(1,1)),验证库功能。
步骤 2:编译测试程序(链接静态库)
命令 :gcc test.c -o test.out -I ./lib/include -L ./lib/mymathlib -lmymath参数解析:
-I ./lib/include:指定头文件搜索路径,让编译器找到mymath.h;-L ./lib/mymathlib:指定库文件搜索路径,让链接器找到libmymath.a;-lmymath:指定要链接的库名(省略lib前缀和.a后缀,链接器会自动拼接为libmymath.a);- 输出:生成可执行文件
test.out,静态库的代码已被完整嵌入其中。
步骤 3:运行可执行文件
命令 :./test.out
- 输出:
1+1=2, errorno=0,验证静态库函数调用成功; - 核心特性:此时删除
libmymath.a,test.out仍可正常运行(静态库代码已嵌入)。
四、Makefile 的作用(自动化编译)
示例中的Makefile将上述步骤自动化:
- 默认目标:编译
mymath.o并归档为libmymath.a; clean目标:清理编译产物(.o/.a/lib目录);output目标:规范整理头文件和库文件目录;通过make/make output/make clean可一键完成编译、整理、清理,提升效率。
核心总结
- 静态库创建核心步骤:编写源码 → 编译生成.o → ar 归档为.a → 规范整理目录;
- 关键命令:
gcc -c(编译.o)、ar -rc(归档.a)、gcc -I -L -l(链接静态库); - 静态库特性:编译时嵌入可执行文件,运行时不依赖原库,具备独立性。
设计一个动态库
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ ll
total 32
-rw-rw-r-- 1 ranjiaju ranjiaju 465 Feb 6 15:10 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 81 Feb 6 14:03 myLog.c
-rw-rw-r-- 1 ranjiaju ranjiaju 56 Feb 6 14:06 myLog.h
-rw-rw-r-- 1 ranjiaju ranjiaju 282 Jan 30 09:42 mymath.c
-rw-rw-r-- 1 ranjiaju ranjiaju 139 Jan 30 09:43 mymath.h
-rw-rw-r-- 1 ranjiaju ranjiaju 125 Feb 6 14:01 myPrint.c
-rw-rw-r-- 1 ranjiaju ranjiaju 47 Feb 6 13:43 myPrint.h
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Feb 6 15:10 testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat myLog.c
#include"myLog.h"
void Log(const char* str)
{
printf("error: %s\n", str);
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat myLog.h
#pragma once
#include<stdio.h>
void Log(const char*);
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat myPrint.c
#include"myPrint.h"
void Print()
{
printf("hello Print\n");
printf("hello Print\n");
printf("hello Print\n");
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat myPrint.h
#pragma once
#include<stdio.h>
void Print();
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cat makefile
dy-lib=libmymethod.so
static-lib=libmymath.a
.PHONY:all
all:$(static-lib) $(dy-lib)
$(dy-lib):myLog.o myPrint.o
gcc -shared -o $@ $^
myLog.o:myLog.c
gcc -fPIC -c myLog.c
myPrint.o:myPrint.c
gcc -fPIC -c myPrint.c
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
.PHONY:clean
clean:
rm -rf *.o *.a *.so mylib
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ make
gcc -c mymath.c
ar -rc libmymath.a mymath.o
gcc -fPIC -c myLog.c
gcc -fPIC -c myPrint.c
gcc -shared -o libmymethod.so myLog.o myPrint.o
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ make output
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ ll
total 64
-rw-rw-r-- 1 ranjiaju ranjiaju 1880 Feb 6 15:14 libmymath.a
-rwxrwxr-x 1 ranjiaju ranjiaju 8224 Feb 6 15:14 libmymethod.so
-rw-rw-r-- 1 ranjiaju ranjiaju 465 Feb 6 15:10 makefile
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 6 15:14 mylib
-rw-rw-r-- 1 ranjiaju ranjiaju 81 Feb 6 14:03 myLog.c
-rw-rw-r-- 1 ranjiaju ranjiaju 56 Feb 6 14:06 myLog.h
-rw-rw-r-- 1 ranjiaju ranjiaju 1560 Feb 6 15:14 myLog.o
-rw-rw-r-- 1 ranjiaju ranjiaju 282 Jan 30 09:42 mymath.c
-rw-rw-r-- 1 ranjiaju ranjiaju 139 Jan 30 09:43 mymath.h
-rw-rw-r-- 1 ranjiaju ranjiaju 1704 Feb 6 15:14 mymath.o
-rw-rw-r-- 1 ranjiaju ranjiaju 125 Feb 6 14:01 myPrint.c
-rw-rw-r-- 1 ranjiaju ranjiaju 47 Feb 6 13:43 myPrint.h
-rw-rw-r-- 1 ranjiaju ranjiaju 1664 Feb 6 15:14 myPrint.o
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Feb 6 15:10 testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ tree mylib
mylib
├── include
│ ├── myLog.h
│ ├── mymath.h
│ └── myPrint.h
└── lib
├── libmymath.a
└── libmymethod.so
2 directories, 5 files
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cp -r mylib testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ Static_Library]$ cd testlib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ll
total 12
-rw-rw-r-- 1 ranjiaju ranjiaju 135 Feb 6 14:59 makefile
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 6 15:15 mylib
-rw-rw-r-- 1 ranjiaju ranjiaju 177 Feb 6 15:07 test.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ cat makefile
test.out:test.c
gcc test.c -o test.out -I ./mylib/include -L ./mylib/lib -lmymath -lmymethod
.PHONY:clean
clean:
rm -rf test.out lib
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ cat test.c
#include"mymath.h"
#include"myLog.h"
#include"myPrint.h"
int main()
{
printf("1+1=%d, errorno=%d\n", add(1, 1), errorno);
Print();
Log("abcef");
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ su
Password:
[root@iZ2vc15k23y9vpuyi3tiqzZ testlib]# cd /etc/ld.so.conf.d
[root@iZ2vc15k23y9vpuyi3tiqzZ ld.so.conf.d]# ll
total 16
-r--r--r-- 1 root root 63 Jun 19 2019 kernel-3.10.0-957.21.3.el7.x86_64.conf
-r--r--r--. 1 root root 63 Nov 9 2018 kernel-3.10.0-957.el7.x86_64.conf
-rw-r--r--. 1 root root 17 Aug 16 2018 mariadb-x86_64.conf
-rw-r--r-- 1 root root 69 Feb 6 15:22 test_dy-lib.conf
[root@iZ2vc15k23y9vpuyi3tiqzZ ld.so.conf.d]# cat test_dy-lib.conf
/home/ranjiaju/test/learning-linux/Static_Library/testlib/mylib/lib
[root@iZ2vc15k23y9vpuyi3tiqzZ ld.so.conf.d]# exit
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ll
total 12
-rw-rw-r-- 1 ranjiaju ranjiaju 135 Feb 6 14:59 makefile
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 6 15:15 mylib
-rw-rw-r-- 1 ranjiaju ranjiaju 177 Feb 6 15:07 test.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ make
gcc test.c -o test.out -I ./mylib/include -L ./mylib/lib -lmymath -lmymethod
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ll
total 24
-rw-rw-r-- 1 ranjiaju ranjiaju 135 Feb 6 14:59 makefile
drwxrwxr-x 4 ranjiaju ranjiaju 4096 Feb 6 15:15 mylib
-rw-rw-r-- 1 ranjiaju ranjiaju 177 Feb 6 15:07 test.c
-rwxrwxr-x 1 ranjiaju ranjiaju 8696 Feb 6 15:23 test.out
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ testlib]$ ./test.out
1+1=2, errorno=0
hello Print
hello Print
hello Print
error: abcef
一、动态库的核心概念
动态库(Shared Library,后缀.so,Shared Object)是 Linux 下的共享库,与静态库(.a)核心差异在于代码加载时机:
- 编译链接阶段:仅将库的符号引用 (函数 / 变量名)写入可执行文件,不会复制库的二进制代码,因此生成的可执行文件体积更小;
- 程序运行阶段:由 Linux 的 ** 动态链接器(
ld.so)** 在程序启动时,从系统中找到对应的动态库并加载到内存,多个程序可共享同一份动态库的内存镜像,节省内存资源; - 命名规则:与静态库一致,必须遵循
lib[库名].so(如实操中的libmymethod.so),链接时可通过-l[库名]简写调用。
二、动态库的完整创建流程(实操对应myLog/myPrint模块)
动态库的创建分4 个核心步骤 ,比静态库多了位置无关代码 的编译要求,这是动态库实现 "共享 / 任意地址加载" 的关键,实操中通过Makefile自动化完成,下面拆解步骤 + 命令。
步骤 1:编写动态库的源码文件(头文件.h + 实现文件.c)
和静态库一样,动态库需要分离接口声明 和逻辑实现,供库的使用者调用:
- 头文件(
myLog.h/myPrint.h):声明对外提供的函数(如Log/Print),用#pragma once防止头文件重复包含,包含依赖的头文件(如stdio.h); - 实现文件(
myLog.c/myPrint.c):实现头文件声明的函数,是动态库的核心逻辑载体。
步骤 2:编译生成位置无关的目标文件(.o)
核心命令 :gcc -fPIC -c myLog.c、gcc -fPIC -c myPrint.c
# Makefile中对应规则
myLog.o:myLog.c
gcc -fPIC -c myLog.c
myPrint.o:myPrint.c
gcc -fPIC -c myPrint.c
- 关键参数
-fPIC:Generate Position-Independent Code ,生成位置无关代码 ;作用:让编译出的目标文件不依赖固定的内存地址,动态库加载时可被放到进程地址空间的任意位置 ,实现多个程序共享同一份动态库,这是动态库与静态库编译目标文件的核心区别 (静态库编译无需-fPIC); - 参数
-c:只编译不链接,仅将.c文件转为二进制目标文件,不做符号解析。 - 实操输出:生成
myLog.o、myPrint.o两个位置无关的目标文件。
步骤 3:将目标文件链接为动态库文件(.so)
核心命令 :gcc -shared -o libmymethod.so myLog.o myPrint.o
# Makefile中对应规则
dy-lib=libmymethod.so
$(dy-lib):myLog.o myPrint.o
gcc -shared -o $@ $^
- 工具:直接用
gcc链接(静态库用ar归档,动态库用gcc+ 参数链接,这是两者的另一关键区别); - 关键参数
-shared:告诉 gcc"生成动态共享库,而非可执行文件",编译器会按动态库的格式打包目标文件; - 自动变量
$@:代表目标文件(libmymethod.so),$^:代表所有依赖文件(myLog.o myPrint.o); - 实操输出:生成动态库核心文件
libmymethod.so(可执行权限,-rwxrwxr-x)。
步骤 4:规范整理动态库(可选但强制推荐)
通过make output将头文件 和动态库文件 整理到统一的发布目录,方便库的使用者调用,和静态库的整理逻辑一致:核心命令 :make output
.PHONY:output
output:
mkdir -p mylib/include # 创建头文件目录
mkdir -p mylib/lib # 创建库文件目录
cp *.h mylib/include # 复制所有头文件到include目录
cp *.so mylib/lib # 复制动态库到lib目录(同时复制静态库*.a)
-p:确保目录不存在时创建,避免报错;- 实操输出:生成
mylib目录,分离include(头文件)和lib(动态库 / 静态库),使用者只需指定这两个目录即可调用库,符合 Linux 库的发布规范。
三、动态库的使用流程(结合静态库一起链接,实操testlib目录)
动态库的使用分编译链接 和运行加载 两步,运行加载是动态库的核心难点(静态库无此步骤),实操中完美解决了路径配置问题,下面拆解步骤。
步骤 1:准备测试程序(test.c)
编写测试代码,包含动态库 + 静态库的头文件,调用对应的函数(add静态库、Print/Log动态库),验证库的功能。
步骤 2:编译测试程序(链接动态库 + 静态库)
核心命令 :gcc test.c -o test.out -I ./mylib/include -L ./mylib/lib -lmymath -lmymethod
test.out:test.c
gcc test.c -o test.out -I ./mylib/include -L ./mylib/lib -lmymath -lmymethod
该命令同时链接了静态库libmymath.a和 动态库libmymethod.so,核心参数和静态库链接一致,解析如下:
-I ./mylib/include:指定头文件搜索路径 ,让编译器找到mymath.h/myLog.h/myPrint.h;-L ./mylib/lib:指定库文件搜索路径 ,让链接器找到libmymath.a和libmymethod.so;-lmymath:链接静态库libmymath.a(省略lib前缀和.a后缀);-lmymethod:链接动态库libmymethod.so(省略lib前缀和.so后缀,链接器会自动匹配);- 实操输出:生成可执行文件
test.out,体积仅 8696 字节(远小于纯静态编译的文件,因为未复制动态库代码)。
步骤 3:配置动态库运行时搜索路径(动态库的核心难点)
这是动态库和静态库最大的区别:静态库编译后无需依赖原库,动态库运行时必须让系统找到对应的.so文件 ,否则会报error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory。
Linux 的动态链接器(ld.so)会从默认搜索路径 (如/lib64、/usr/lib64)加载动态库,自定义的动态库(实操中在testlib/mylib/lib)不在默认路径,因此需要手动配置路径 ,实操中用了系统级永久配置(推荐生产环境使用),步骤拆解:
- 切换 root 用户:
su(系统级配置需要管理员权限); - 进入动态库配置目录:
cd /etc/ld.so.conf.d(Linux 专门存放自定义库路径的目录,所有.conf文件会被动态链接器解析); - 创建配置文件:
test_dy-lib.conf(文件名任意,后缀必须为.conf); - 写入动态库的绝对路径 :
/home/ranjiaju/test/learning-linux/Static_Library/testlib/mylib/lib(必须绝对路径,相对路径无效); - (实操中省略但必须执行 )更新动态链接器缓存:
ldconfig(让系统加载新的配置文件,生成库的缓存索引,否则配置不生效); - 退出 root:
exit。
补充 :除了系统级永久配置,还有用户级临时配置(适合测试):
# 临时设置环境变量,仅当前终端有效
export LD_LIBRARY_PATH=./mylib/lib:$LD_LIBRARY_PATH
步骤 4:运行可执行文件,验证动态库
核心命令 :./test.out运行时,动态链接器会根据配置的路径找到libmymethod.so并加载到内存,同时静态库的代码已嵌入可执行文件,最终正常执行并输出结果:
plaintext
1+1=2, errorno=0
hello Print
hello Print
hello Print
error: abcef
验证动态库(Print/Log)和静态库(add)均调用成功。
四、Makefile 的自动化逻辑(动态 + 静态库一体化构建)
实操中的Makefile设计非常规范,实现了静态库 + 动态库的一体化自动化构建,核心亮点:
- 定义目标变量:
dy-lib=libmymethod.so、static-lib=libmymath.a,简化后续规则; - 全局目标
all:all:$(static-lib) $(dy-lib),执行make时会同时构建静态库和动态库; - 分离编译规则:静态库目标文件无需
-fPIC,动态库目标文件强制-fPIC,符合两者特性; - 伪目标
clean:清理所有编译产物(.o/.a/.so/mylib),避免冗余文件; - 伪目标
output:统一整理头文件和库文件,实现库的标准化发布。
五、动态库相关核心命令汇总
结合实操,将动态库创建、使用、配置的核心命令整理成表,方便记忆:
| 操作场景 | 核心命令 | 关键参数 / 说明 |
|---|---|---|
| 编译位置无关目标文件 | gcc -fPIC -c 源文件.c |
-fPIC:生成位置无关代码,动态库必备 |
| 链接生成动态库 | gcc -shared -o 库名.so 目标文件.o |
-shared:生成动态共享库;库名遵循libxxx.so |
| 编译链接动态库(测试程序) | gcc 测试代码.c -o 可执行文件 -I 头文件路径 -L 库路径 -l库名 |
-I:头文件路径;-L:库文件路径;-l:简写库名(省略 lib/.so) |
| 系统级配置动态库路径 | 1. echo 库绝对路径 > /etc/ld.so.conf.d/xxx.conf2. ldconfig |
步骤 2 必须执行,更新动态链接器缓存 |
| 用户级临时配置动态库路径 | export LD_LIBRARY_PATH=库路径:$LD_LIBRARY_PATH |
仅当前终端有效,测试用 |
六、动态库与静态库的核心操作差异(结合本次实操)
本次实操同时使用了静态库和动态库,能清晰体现两者的操作区别,核心差异只有 2 点:
- 编译目标文件:动态库需要
-fPIC,静态库无需; - 生成库文件:动态库用
gcc -shared链接,静态库用ar -rc归档; - 运行时依赖:动态库需要配置路径,静态库无需(代码已嵌入)。
其余步骤(编写源码、整理目录、链接测试程序的-I/-L/-l参数)完全一致。
核心总结
动态库的设计和使用核心围绕3 个关键 和4 个步骤:
- 3 个关键 :
-fPIC(位置无关代码)、-shared(生成动态库)、运行时路径配置(动态链接器找到库); - 创建 4 步骤 :编写源码→编译
-fPIC目标文件→gcc -shared链接生成.so→规范整理目录; - 使用 3 步骤 :编写测试代码→
gcc -I/-L/-l链接生成可执行文件→配置动态库路径并运行。
本次实操是 Linux 库开发的标准流程,既实现了动态库的创建和使用,又结合静态库体现了两者的兼容性,还解决了动态库运行时路径配置的核心难点,非常具有参考性。
深度刨析fPIC(与位置无关码)
一、为什么动态库必须用 -fPIC?从动态库的加载困境说起
要理解 -fPIC,首先要回到动态库的核心特性:运行时动态加载、多进程共享内存。这两个特性直接导致了 "固定地址加载" 的不可行性,具体体现在:
1. 地址冲突与 ASLR 防护
- 现代操作系统为了安全性,会启用 ASLR(地址空间布局随机化),每次加载动态库时,都会随机分配虚拟内存地址,不会固定在某个位置。
- 如果动态库代码使用绝对地址,一旦该地址在进程中被其他模块占用,或因 ASLR 导致加载地址变化,就会出现地址错误,程序崩溃。
2. 多进程共享的需求
动态库的核心价值是多进程共享同一份内存镜像。如果每个进程加载动态库时都需要修改代码中的绝对地址(重定位),就会破坏共享性(每个进程都需要一份修改后的副本),失去动态库节省内存的意义。
二、-fPIC(Position Independent Code):位置无关码的核心原理
-fPIC 的本质是让动态库的代码不依赖任何固定虚拟地址 ,仅通过相对偏移量 和间接寻址实现函数调用与数据访问,从而支持 "在虚拟内存中任意位置加载"(如图中标注)。
1. 核心思想:用 "相对偏移" 替代 "绝对地址"
假设 libc.so 的起始加载地址为 0x90000,printf 函数在库中的相对偏移量 是 0x1122,那么 printf 的实际地址就是:
实际地址 = 库的起始地址 + 相对偏移量
0x90000 + 0x1122 = 0x91122
无论 libc.so 被加载到哪个起始地址(如 0xA0000),只要偏移量 0x1122 固定,就能通过 "起始地址 + 偏移" 计算出正确的函数地址,完全摆脱对固定地址的依赖。
2. 技术实现:相对跳转与 GOT/PLT 表
- 内部函数调用 :动态库内部的函数调用使用相对跳转指令 (如
call 0x1122),这里的0x1122是相对于当前指令的偏移量,而非绝对地址,因此无论库加载到哪里,跳转都能正确执行。 - 全局数据 / 外部函数访问 :通过 GOT(全局偏移表) 和 PLT(过程链接表) 实现间接寻址。GOT 存储全局变量和外部函数的地址偏移,第一次访问时动态解析,后续直接复用 GTO 中的偏移,保证位置无关性。
三、静态库为什么不需要 -fPIC?
静态库与动态库的加载逻辑完全不同,因此不需要位置无关码:
- 静态库 :编译时会将代码完整复制到可执行文件中,可执行文件的虚拟地址是编译时确定的绝对地址,加载时直接映射到固定虚拟地址,无需动态加载或重定位,自然不需要位置无关码。
- 动态库 :运行时才加载到虚拟内存,地址不固定,必须通过
-fPIC生成位置无关码,才能支持任意地址加载与多进程共享。
四、-fPIC 的核心价值
- 支持动态加载与地址随机化:让动态库可以加载到虚拟内存的任意位置,配合 ASLR 大幅提升系统安全性。
- 实现高效内存共享:多进程可共享同一份动态库的内存镜像(无需每个进程重定位),显著节省内存资源。
- 提升兼容性:动态库无需依赖固定地址,可在不同进程、不同系统版本中稳定运行。