Linux ——— 静态库和动态库的设计与使用

目录

设计一个静态库

一、静态库的核心概念

[二、静态库的创建流程(完整步骤 + 命令解析)](#二、静态库的创建流程(完整步骤 + 命令解析))

[三、静态库的使用流程(测试程序编译 + 运行)](#三、静态库的使用流程(测试程序编译 + 运行))

[四、Makefile 的作用(自动化编译)](#四、Makefile 的作用(自动化编译))

核心总结
设计一个动态库

一、动态库的核心概念

二、动态库的完整创建流程(实操对应myLog/myPrint模块)

三、动态库的使用流程(结合静态库一起链接,实操testlib目录)

[四、Makefile 的自动化逻辑(动态 + 静态库一体化构建)](#四、Makefile 的自动化逻辑(动态 + 静态库一体化构建))

五、动态库相关核心命令汇总

六、动态库与静态库的核心操作差异(结合本次实操)

核心总结
深度刨析fPIC(与位置无关码)

[一、为什么动态库必须用 -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.atest.out仍可正常运行(静态库代码已嵌入)。

四、Makefile 的作用(自动化编译)

示例中的Makefile将上述步骤自动化:

  1. 默认目标:编译mymath.o并归档为libmymath.a
  2. clean目标:清理编译产物(.o/.a/lib目录);
  3. output目标:规范整理头文件和库文件目录;通过make/make output/make clean可一键完成编译、整理、清理,提升效率。

核心总结

  1. 静态库创建核心步骤:编写源码 → 编译生成.o → ar 归档为.a → 规范整理目录;
  2. 关键命令:gcc -c(编译.o)、ar -rc(归档.a)、gcc -I -L -l(链接静态库);
  3. 静态库特性:编译时嵌入可执行文件,运行时不依赖原库,具备独立性。

设计一个动态库

复制代码
[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.cgcc -fPIC -c myPrint.c

复制代码
# Makefile中对应规则
myLog.o:myLog.c
	gcc -fPIC -c myLog.c
myPrint.o:myPrint.c
	gcc -fPIC -c myPrint.c
  • 关键参数-fPICGenerate Position-Independent Code ,生成位置无关代码 ;作用:让编译出的目标文件不依赖固定的内存地址,动态库加载时可被放到进程地址空间的任意位置 ,实现多个程序共享同一份动态库,这是动态库与静态库编译目标文件的核心区别 (静态库编译无需-fPIC);
  • 参数-c:只编译不链接,仅将.c文件转为二进制目标文件,不做符号解析。
  • 实操输出:生成myLog.omyPrint.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.alibmymethod.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)不在默认路径,因此需要手动配置路径 ,实操中用了系统级永久配置(推荐生产环境使用),步骤拆解:

  1. 切换 root 用户:su(系统级配置需要管理员权限);
  2. 进入动态库配置目录:cd /etc/ld.so.conf.d(Linux 专门存放自定义库路径的目录,所有.conf文件会被动态链接器解析);
  3. 创建配置文件:test_dy-lib.conf(文件名任意,后缀必须为.conf);
  4. 写入动态库的绝对路径/home/ranjiaju/test/learning-linux/Static_Library/testlib/mylib/lib(必须绝对路径,相对路径无效);
  5. (实操中省略但必须执行 )更新动态链接器缓存:ldconfig(让系统加载新的配置文件,生成库的缓存索引,否则配置不生效);
  6. 退出 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设计非常规范,实现了静态库 + 动态库的一体化自动化构建,核心亮点:

  1. 定义目标变量:dy-lib=libmymethod.sostatic-lib=libmymath.a,简化后续规则;
  2. 全局目标allall:$(static-lib) $(dy-lib),执行make时会同时构建静态库和动态库
  3. 分离编译规则:静态库目标文件无需-fPIC,动态库目标文件强制-fPIC,符合两者特性;
  4. 伪目标clean:清理所有编译产物(.o/.a/.so/mylib),避免冗余文件;
  5. 伪目标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 点:

  1. 编译目标文件:动态库需要-fPIC,静态库无需;
  2. 生成库文件:动态库用gcc -shared链接,静态库用ar -rc归档;
  3. 运行时依赖:动态库需要配置路径,静态库无需(代码已嵌入)。

其余步骤(编写源码、整理目录、链接测试程序的-I/-L/-l参数)完全一致。

核心总结

动态库的设计和使用核心围绕3 个关键4 个步骤

  1. 3 个关键-fPIC(位置无关代码)、-shared(生成动态库)、运行时路径配置(动态链接器找到库);
  2. 创建 4 步骤 :编写源码→编译-fPIC目标文件→gcc -shared链接生成.so→规范整理目录;
  3. 使用 3 步骤 :编写测试代码→gcc -I/-L/-l链接生成可执行文件→配置动态库路径并运行。

本次实操是 Linux 库开发的标准流程,既实现了动态库的创建和使用,又结合静态库体现了两者的兼容性,还解决了动态库运行时路径配置的核心难点,非常具有参考性。


深度刨析fPIC(与位置无关码)

一、为什么动态库必须用 -fPIC?从动态库的加载困境说起

要理解 -fPIC,首先要回到动态库的核心特性:运行时动态加载、多进程共享内存。这两个特性直接导致了 "固定地址加载" 的不可行性,具体体现在:

1. 地址冲突与 ASLR 防护

  • 现代操作系统为了安全性,会启用 ASLR(地址空间布局随机化),每次加载动态库时,都会随机分配虚拟内存地址,不会固定在某个位置。
  • 如果动态库代码使用绝对地址,一旦该地址在进程中被其他模块占用,或因 ASLR 导致加载地址变化,就会出现地址错误,程序崩溃。

2. 多进程共享的需求

动态库的核心价值是多进程共享同一份内存镜像。如果每个进程加载动态库时都需要修改代码中的绝对地址(重定位),就会破坏共享性(每个进程都需要一份修改后的副本),失去动态库节省内存的意义。

二、-fPIC(Position Independent Code):位置无关码的核心原理

-fPIC 的本质是让动态库的代码不依赖任何固定虚拟地址 ,仅通过相对偏移量间接寻址实现函数调用与数据访问,从而支持 "在虚拟内存中任意位置加载"(如图中标注)。

1. 核心思想:用 "相对偏移" 替代 "绝对地址"

假设 libc.so 的起始加载地址为 0x90000printf 函数在库中的相对偏移量0x1122,那么 printf 的实际地址就是:

复制代码
实际地址 = 库的起始地址 + 相对偏移量
0x90000 + 0x1122 = 0x91122

无论 libc.so 被加载到哪个起始地址(如 0xA0000),只要偏移量 0x1122 固定,就能通过 "起始地址 + 偏移" 计算出正确的函数地址,完全摆脱对固定地址的依赖。

2. 技术实现:相对跳转与 GOT/PLT 表

  • 内部函数调用 :动态库内部的函数调用使用相对跳转指令 (如 call 0x1122),这里的 0x1122 是相对于当前指令的偏移量,而非绝对地址,因此无论库加载到哪里,跳转都能正确执行。
  • 全局数据 / 外部函数访问 :通过 GOT(全局偏移表)PLT(过程链接表) 实现间接寻址。GOT 存储全局变量和外部函数的地址偏移,第一次访问时动态解析,后续直接复用 GTO 中的偏移,保证位置无关性。

三、静态库为什么不需要 -fPIC

静态库与动态库的加载逻辑完全不同,因此不需要位置无关码:

  • 静态库 :编译时会将代码完整复制到可执行文件中,可执行文件的虚拟地址是编译时确定的绝对地址,加载时直接映射到固定虚拟地址,无需动态加载或重定位,自然不需要位置无关码。
  • 动态库 :运行时才加载到虚拟内存,地址不固定,必须通过 -fPIC 生成位置无关码,才能支持任意地址加载与多进程共享。

四、-fPIC 的核心价值

  1. 支持动态加载与地址随机化:让动态库可以加载到虚拟内存的任意位置,配合 ASLR 大幅提升系统安全性。
  2. 实现高效内存共享:多进程可共享同一份动态库的内存镜像(无需每个进程重定位),显著节省内存资源。
  3. 提升兼容性:动态库无需依赖固定地址,可在不同进程、不同系统版本中稳定运行。
相关推荐
陌上花开缓缓归以2 小时前
linux mtd-utils使用源码分析(ubuntu测试版)
linux·arm开发·ubuntu
wangjialelele2 小时前
Linux下的IO操作以及ext系列文件系统
linux·运维·服务器·c语言·c++·个人开发
HypoxiaDream3 小时前
LINUX-Ext系列⽂件系统
linux·运维·服务器
小毛驴8503 小时前
Linux curl 命令用法
linux·运维·chrome
李斯啦果3 小时前
【Linux】Linux目录配置
linux·运维·服务器
AI+程序员在路上3 小时前
linux下线程中pthread_detach与pthread_join区别
linux·运维·服务器
代码游侠3 小时前
C语言核心概念复习——C语言基础阶段
linux·开发语言·c++·学习
logocode_li3 小时前
说透 Linux Shell:命令与语法的底层执行逻辑
linux·运维·ssh
CHENKONG_CK3 小时前
晨控CK-LR08-E00与汇川H5U系列PLC配置MODBUSTCP通讯连接手册
linux·服务器·网络