☰
【金丹·87】静态库和动态库:编译、链接和加载
2026/10/15 2:31:21 网站建设 项目流程

静态库
动态库
Linux
静态库
动态库
Linux
静态库
动态库
Linux
静态库
动态库
静态库
动态库
Linux

【金丹·87】静态库和动态库:编译、链接和加载

码农修仙传 · 金丹期 · 第87篇
我是玄芯散人,带你从炼气修到大乘。


境界标识

╔══════════════════════════════════╗ ║ 金丹期 · 第87篇 ║ ║ 静态库和动态库 ║ ║ ar / gcc -shared -fPIC ║ ║ dlopen / dlsym / soname ║ ║ 预计阅读:28 分钟 ║ ╚══════════════════════════════════╝

修仙引入

066 那篇把链接报错的五种情境摸过一遍,也顺嘴提了一句静态库 vs 动态库的命令长什么样。这一篇算「认门」,弟子看完知道ar rcs能打.a、gcc -shared -fPIC能出.so。认完了门,再往里走一寸发现全是问题:库怎么被打包出来的,符号怎么决定谁能看见外头,库的版本号怎么让江湖上无数弟子同时跑着不打架,程序跑起来后又怎么临场把一个库拉进进程里。

修真界把这一关叫「炼器」。064 讲的是「矿石怎么熔」的理论,066 讲的是「铸完发现裂了怎么办」的补救。087 要讲四件事:先把矿石提纯,再把料铸成灵器,再教弟子战场上临阵拔刀,最后给灵器登记门牌。这一篇把库的制作端和运行时加载端拆开看一遍。


硬核主体

066 静态库 vs 动态库回顾:本篇要往哪里走

066 在「静态库 vs 动态库:链接行为的实战差异」那一节里写过两个命令,一个是ar rcs libfoo.a foo.o bar.o baz.o,一个是gcc -fPIC -shared foo.c -o libfoo.so。066 那一篇算把「使用端」点到了。

可 066 没碰的是「制作端」。弟子把foo.c写成.o,再用ar把若干.o打成一个.a,这一段过程 066 没展开。「ar的rcs三个选项分别什么意思」「为什么做出来的.so必须用-fPIC」「dlopen跟普通的链接有什么区别」「符号表里那么一大片函数,哪些会被别人看见,哪些不会」「soname 这个名字到底管什么用」。这些 066 没答。

修真比喻:066 是修真界的「辨器」课,弟子学会了认出剑与刀的区别。087 是「铸器」课,弟子从铸剑到磨刀到拔刀到上户口,每一步都要看一遍。

静态库的制作:ar把.o打成.a

修真界里.a文件实际上是若干.o的归档。归档这个动作不需要链接器,只需要ar(archive)这一把工具。ar是 GNU binutils 里的老工具,比 gcc 还老,专门做「把多个文件打成一捆」这件事。

修真界里最常用的命令长这样:

# 先编译每个 .c 成 .o(-c 表示只编译不链接)$ gcc-cadd.c sub.c mul.c# 用 ar 把三个 .o 打成一个静态库# rcs 三个字母分别是:r=替换/插入、c=创建归档、s=写索引$ ar rcs libmath.a add.o sub.o mul.o

修真界里ar rcs三个选项各管一摊事。r(replace/insert)把同名成员替换掉,没有就插入。c(create)归档不存在就创建。s(write index)写一份符号索引,让链接器能从归档里直接定位「哪个.o定义了哪个符号」,不用全表扫描。这三个选项捆在一起用是标准做法,少了s链接器会慢很多,少了c第一次创建会失败。

修真界里还有几个常用的子命令值得记:

# t:列出归档里的所有成员$ ar t libmath.a add.o sub.o mul.o# x:把成员抽出来$ ar x libmath.a add.o# d:删除成员$ ar d libmath.a mul.o# --help 看所有选项$ ar--help

修真界里.a文件的物理格式很简单,就是把每个.o原封不动贴一起,前面加一个简单的全局头(叫/这个特殊成员)。所以.a里的.o跟磁盘上独立的.o在 ELF 视图里是完全等价的,链接器看到-lfoo时会从libfoo.a里挑出能解决当前未定义符号的那个.o,整段整段塞进可执行文件。

修真界里做一组动手演示,把三个.o打成一个静态库,再链接出可执行文件:

// math.hintadd(inta,intb);intsub(inta,intb);// add.c#include"math.h"intadd(inta,intb){returna+b;}// sub.c#include"math.h"intsub(inta,intb){returna-b;}
// main.c#include<stdio.h>#include"math.h"intmain(void){printf("%d\n",add(3,4));// 期望 7printf("%d\n",sub(10,3));// 期望 7return0;}
$ gcc-cadd.c sub.c $ ar rcs libmath.a add.o sub.o $ gcc main.c -L.-lmath-oapp $ ./app77

修真界里-L.把当前目录加进库的搜索路径,-lmath是「找libmath.a或libmath.so」的简写。链接器按顺序扫描,发现add和sub两个未定义符号都能在libmath.a里解决,就把对应的.o整个拉进可执行文件。066 已经讲过库顺序的细节(被依赖的库要放后面),本篇不重复。

修真比喻:静态库是修真界的「手抄本」,弟子把宗门功法一页页抄一份揣身上,副本多就占地方。动态库是修真界的「玉简传功」,所有人到宗门同一个玉简那里读取,省地方但宗门得一直开着。

动态库的制作:gcc -shared -fPIC出.so

修真界里.a是装订成册的印本,.so不一样。.so里的代码要在运行时被加载到任意地址,还要在多个进程之间共享同一份物理页。所以制作.so时有两件事比.a多一步。

第一件,加-shared让 gcc 调链接器产出动态库而不是可执行文件。第二件,加-fPIC(Position Independent Code,位置无关代码)让编译器生成的代码用相对地址访问,不依赖加载地址。这两件事拆开说:

# 第一步:编译时加 -fPIC,生成位置无关的 .o$ gcc-c-fPICadd.c sub.c# 第二步:链接时加 -shared,产出 .so$ gcc-shared-olibmath.so add.o sub.o# 也可以一步到位$ gcc-fPIC-sharedadd.c sub.c-olibmath.so

修真界里-fPIC单独写是给编译器听的,-shared单独写是给链接器听的。两件事可以合在一条命令里,但语义上各管各的:-fPIC决定代码生成,-shared决定最终产物类型。把这两件事分清楚,写 CMakeLists 或者 Makefile 时就不会迷糊。

修真界里做出来的.so可以用file看一眼:

$filelibmath.so libmath.so: ELF64-bit LSB shared object, x86-64, version1(SYSV), dynamically linked,...

修真界里这条输出里的「shared object」就是动态库的 ELF 类型缩写,跟可执行文件的「executable」是两回事。

修真比喻:动态库是修真界的「玉简传功」,弟子不揣走原件,需要时去宗门玉简那里抄阅。宗门里只有一份原件,所有弟子都对着同一份读,省地方,但要靠宗门一直开着。

为什么需要-fPIC:修真界的「坐标重写」

修真界里-fPIC看着像一个可有可无的开关,实际上是动态库的硬性要求。这事得从代码怎么访问「自己」讲起。

修真界里一段普通的 C 代码如果要访问一个全局变量,比如int counter = 0; counter += 1;,编译器会生成这样的指令:

mov rax, [rip + 0x2a3a] ; 把 0x2a3a 当成「counter 的地址」 add dword [rax], 1

修真界里麻烦出在两处。第一处是函数调用别的函数。如果函数 A 在编译时知道 B 在自己前方 100 字节的位置,编译器会生成call 0x401050这种「绝对地址调用」。代码段被加载到0x401050上没问题,加载到0x7f0000001000上就跳错了。第二处是访问函数自身的地址(拿函数指针等),这事在 C 代码里少碰,但编译器内部实现「可重入」代码时绕不开。

修真界里-fPIC就是为这两处准备的。GCC 的做法是把代码里所有「跳到固定地址」的地方改成「先查一张表(GOT, Global Offset Table),表里的地址再指过去」。.so加载到内存时,动态链接器(ld-linux.so)负责把这张表填好。所以叫「位置无关」:代码本身不写死任何绝对地址,全靠 GOT 中转,加载到哪都能跑。

修真界里 32 位 x86 上还有更复杂的麻烦。32 位没法用 RIP 相对寻址访问数据,必须先算出 GOT 的地址再取数据。GCC 在 32 位上用ebx寄存器专门存 GOT 的地址,每个函数入口都得做call __x86.get_pc_thunk.bx; add ebx, ...这种开胃菜,开销比 64 位大一截。所以 64 位上偶尔有人想省一点性能会加-fno-pic,但开了会带来额外开销,不建议这么干。

修真比喻:修真界的功法书页是用相对坐标写的,「前三行」「下面第七行」这种描述,不写「第 401050 页」。分舵把书页放在哪都一样读。-fPIC是修真界的「坐标重写法」,把书页从绝对页码改成相对坐标,不重写法直接拿过去读,分舵挪个位置就全错位了。

dlopen/dlsym/dlerror/dlclose:修真界的临阵拔刀

修真界里前面的.so用法都假设「编译时知道要链接哪个库」。可修真界里还有一类用法:编译时完全不知道程序会用到哪个库,要等运行时才决定。比如一个播放器,支持的编码格式由用户在配置文件里指定。比如一个 Web 服务器,第三方模块以.so文件形式放在某个目录里,服务器启动时扫一遍目录,把每个.so都拉进来。比如 Python 的 C 扩展、Emacs 的 dynamic module,都走这条路。

修真界里这条路叫「动态加载」(dynamic loading),POSIX 标准给了四个函数:

#include<dlfcn.h>void*dlopen(constchar*pathname,intmode);void*dlsym(void*handle,constchar*symbol);intdlclose(void*handle);char*dlerror(void);

修真界里这四个函数的分工很清楚。dlopen打开一个.so,返回一个不透明的句柄(handle)。dlsym在句柄里查一个符号,返回它的地址(强转成函数指针用)。dlclose关掉句柄,引用计数减一,引用归零时真的卸载。dlerror返回最近一次失败的错误信息,没失败就返NULL。

修真界里dlopen的第二个参数mode有两个最常用的值:RTLD_LAZY(函数引用延迟到第一次调用时才解析)和RTLD_NOW(dlopen调用时立刻把全部符号解析完)。RTLD_LAZY效率更高但风险是漏网的符号错误要到运行时才暴露;RTLD_NOW启动慢一点但出错立刻现形。实际项目里推荐用RTLD_NOW,启动时报错比跑到一半崩溃好排查得多。RTLD_GLOBAL让这个库导出的符号进入全局命名空间,RTLD_LOCAL(默认)把符号限制在本库内。

修真界里把四个函数串起来写一段标准的「插件加载器」:

// plugin_loader.c#include<dlfcn.h>#include<stdio.h>#include<stdlib.h>typedefint(*plugin_init_fn)(void);typedefint(*plugin_run_fn)(constchar*arg);intmain(intargc,char**argv){if(argc<2){fprintf(stderr,"用法: %s <plugin.so>\n",argv[0]);return1;}// RTLD_NOW 让所有未定义符号当场报错void*handle=dlopen(argv[1],RTLD_NOW);if(!handle){fprintf(stderr,"dlopen 失败: %s\n",dlerror());return1;}// 清掉 dlopen 可能产生的错误信息dlerror();// 取两个符号:初始化函数和运行函数plugin_init_fn init=(plugin_init_fn)dlsym(handle,"plugin_init");constchar*err=dlerror();if(err){fprintf(stderr,"找 plugin_init 失败: %s\n",err);dlclose(handle);return1;}plugin_run_fn run=(plugin_run_fn)dlsym(handle,"plugin_run");err=dlerror();if(err){fprintf(stderr,"找 plugin_run 失败: %s\n",err);dlclose(handle);return1;}if(init()!=0){fprintf(stderr,"插件初始化失败\n");dlclose(handle);return1;}intrc=run("hello world");printf("插件返回 %d\n",rc);dlclose(handle);return0;}
// plugin_hello.c(要编译成 libhello.so)#include<stdio.h>intplugin_init(void){printf("hello 插件初始化\n");return0;}intplugin_run(constchar*arg){printf("hello 插件跑起来: %s\n",arg);return0;}
$ gcc-fPIC-sharedplugin_hello.c-olibhello.so $ gcc plugin_loader.c-oplugin_loader-ldl$ ./plugin_loader ./libhello.so hello 插件初始化 hello 插件跑起来: hello world hello 插件返回0

修真界里-ldl是链接libdl.so,那一行提供dlopen/dlsym等函数的实现。dlsym还有两个特殊句柄:RTLD_DEFAULT按默认搜索顺序查找符号,RTLD_NEXT从当前库之后的库开始查,常用于「hook 别人的函数」。LD_PRELOAD那种劫持malloc的玩法,靠的就是RTLD_NEXT。

修真比喻:动态加载是修真界的「临阵拔刀」。弟子进山门前不知道会遇上哪种妖兽,出招前也没法把刀库全背在身上。临场从百宝囊(dlopen)里掏一把刀(dlsym),用完塞回去(dlclose)。掏错刀(找不到符号)百宝囊会吐一张小纸条(dlerror)说明白到底哪儿错了。

符号可见性:修真界的「谁能看见这把剑」

修真界里编译出来的.so文件里,所有「非 static」的 C 函数和非 static 全局变量,默认都进了动态符号表。这意思是:nm -D libfoo.so默认能看到一堆符号,全是公开的。

修真界里这背后有三个问题。第一个问题,库里某些「只给自己用」的辅助函数被暴露出来,使用方一不小心声明了同名函数就冲突了,C++ 名字修饰(name mangling)不一致时尤其乱。第二个问题,库里任何内部数据结构一改,整个库的 ABI(二进制接口约定)就变了,所有依赖这个库的可执行文件都得重编译。第三个问题,符号表越大,动态链接器解析时间越长,库启动越慢。

修真界里要修这三件事,根子是「符号可见性」(symbol visibility)。可见性有四种值,GCC 上是default、hidden、internal、protected:

值行为
default默认,符号进入动态符号表,别人能看见
hidden符号不进动态符号表,外部看不见,但函数内仍可调
internal比 hidden 还严,汇编阶段就当本地符号用
protected符号被导出,但同一库内调用不经过 PLT,性能更好

修真界里最常用的一招是把整个库默认设成 hidden,再单独挑几个想暴露的函数设成 default。这是「白名单」模式,比默认全暴露再单挑隐藏(「黑名单」模式)安全得多。

写法上用编译参数-fvisibility=hidden给整个编译单元默认隐藏,再用__attribute__((visibility("default")))给单个函数开窗。实际项目里更常见的做法是用「导出宏」收敛到一个头文件,方便跨 Windows / Linux 维护:

// foo_export.h#ifdef_WIN32#defineFOO_EXPORT__declspec(dllexport)#else#defineFOO_EXPORT__attribute__((visibility("default")))#endifintFOO_EXPORTpublic_api(intx);

这种宏叫FOO_EXPORT(命名按库名走),库的每个公开函数都加这个宏,内部函数一个都不加。-fvisibility=hidden作为全局开关配上FOO_EXPORT作为白名单,是工业界做公共库的标准做法。Linux 上 glibc、各大开源 C 库都这么写。Windows 上没有 visibility 属性,用的是__declspec(dllexport)/__declspec(dllimport),宏就是为了在两边都写一份兼容代码。

修真比喻:可见性是修真界的「传功规矩」。宗门里有些功法可以外传(default),有些只能内部弟子修(hidden)。如果不分清楚,外面弟子胡乱喊一招内部功法的名字,要么没人应答(链接报错),要么窜了真法走火入魔(ABI 不兼容崩溃)。修真界正经宗门的规矩是「外传的有目录,内部的不出声」,这套规矩在 gcc 上叫-fvisibility=hidden+ 白名单宏。

soname:动态库的「身份证号」

修真界里.so文件的「名字」比想象复杂。一个.so实际有三种名字:real name、soname、linker name。三者的关系修真界里必须搞清楚。

修真界里real name是磁盘上文件的真名,必须带主版本+次版本+修订号的命名,例如libfoo.so.1.0.0。这一段是文件物理实体,动态链接器加载时按这个名字找文件。soname是「库的身份号」,记录在.so文件的.dynamic段里的DT_SONAME字段里,例如libfoo.so.1。可执行文件的「我需要哪些.so」清单里(DT_NEEDED),登记的是 soname 不是 real name。这一招让 soname 在 bug fix 升级(比如libfoo.so.1.0.0替换为libfoo.so.1.0.5)时不用动可执行文件。linker name(也叫 developer name)是编译时-lfoo找的那个名字,比如libfoo.so,通常是个符号链接指向soname或real name。

修真界里三者的关系画出来:

渲染错误:Mermaid 渲染失败: Lexical error on line 12. Unrecognized text. .../usr/lib/libfoo.so.1] H --> I[li -----------------------^

修真界里给库设 soname 的做法是是在链接时加-Wl,-soname:

$ gcc-fPIC-shared-Wl,-soname,libmath.so.1 add.c sub.c-olibmath.so.1.0.0# 查看 soname$ readelf-dlibmath.so.1.0.0|grepSONAME 0x000000000000000e(SONAME)Library soname:[libmath.so.1]# 创建 soname 符号链接(修真界约定俗成)$ln-slibmath.so.1.0.0 libmath.so.1 $ln-slibmath.so.1.0.0 libmath.so# 编译期链接$ gcc main.c -L.-lmath-oapp# 链接器找 libmath.so → libmath.so.1 → libmath.so.1.0.0

修真界里 soname 的版本号设计有讲究。约定俗成:主版本号(major)变了表示 ABI 不兼容,所有依赖这个库的可执行文件都得重编译;从版本号(minor)变了表示加了新功能但 ABI 兼容;修订号(patch)变了只是 bug fix。这条规矩让发行版(比如 Debian)的「依赖管理」成为可能。库装到/usr/lib后,ldconfig会扫一遍所有.so,把每个 soname 对应的文件登记到/etc/ld.so.cache,动态链接器加载时直接查缓存不扫目录。

/* libmath.version */ LIBABC_1.0 { global: add; sub; local: *; /* 其他所有符号都隐藏 */ };
$ gcc-fPIC-shared-Wl,--version-script,libmath.version\add.c sub.c-olibmath.so.1.0.0

修真界里这个 map 文件格式是 ld 脚本的子集,叫「linker version script」。它不仅管「暴露不暴露」,还管「这个符号属于哪个版本」,动态链接器加载时能精准挑出版本对应的符号。大库里 glibc、libstdc++ 都用这个机制保证 ABI 兼容性。

修真比喻:soname 是修真界的「灵器身份证号」。一把剑(real namelibfoo.so.1.0.0)有多个名字:弟子入门时喊的剑号(linker namelibfoo.so)、江湖登记的门派代号(sonamelibfoo.so.1)、以及物理上这把剑的真身。只要门派代号不变,剑怎么磨都还是这把剑;门派代号一变(比如libfoo.so.2),就说明这把剑被宗门重新铸造过了,所有用旧剑的功法(可执行文件)都得回炉重修。

静态库 vs 动态库:修真界的决策树

修真界里实战项目总要在两种库之间选。把这一节做成决策树,方便弟子按症状开药。

修真界里先看四个主要差异:

对比项静态库.a动态库.so
链接时机链接时整段复制进可执行链接时只留 DT_NEEDED 清单,运行时加载
可执行文件大小大(含库代码)小(只含 GOT/PLT 引用)
启动速度快(不需要加载外部.so)慢(要扫 DT_NEEDED、解析符号)
升级库必须重链接可执行文件直接替换.so,可执行不用动
部署复杂度简单,单文件复杂,要保证目标机器有对版本的.so
-fPIC必需否是

修真界里按四种情况做选择:

第一种情况,库要被多个进程用,且进程之间需要共享物理内存。Linux 系统库走这条路,比如 glibc 和 libpthread 都是.so,bash、ssh、vim几十个进程同时在跑,全都链libc.so.6,物理内存里实际只占一份。如果每个进程都把 libc 静态链进去,几百兆物理内存就没了。

第二种情况,库很少更新,且对启动速度敏感。嵌入式 Linux 设备经常这么干:所有依赖打包成一个静态二进制,部署到设备上,不依赖/usr/lib里有什么。这种「单文件可执行」部署起来最省心,不用考虑目标机的库版本。Docker 容器里的 Go 程序也走这条路线。

第三种情况,库要作为插件机制的一部分。这一条就是上一节dlopen的领域,必须是.so,不能用.a。因为.a在编译期就被复制进可执行了,没法「运行时临时加载」。

第四种情况,库的 ABI 不稳定,还在快速迭代。这种情况下用.a更安全:.a把代码复制进可执行,避免了升级.so时不同进程之间互相干扰。代价是每次库更新都要重链整个项目。

修真比喻:修真界里选静态库还是动态库,跟选「自带干粮」还是「到分舵吃饭」一个道理。自带干粮(静态)省心但累赘,路上补给要够。到分舵吃饭(动态)轻装上阵,但分舵关门(.so找不到)就饿肚子。长途任务(嵌入式部署)自带干粮,开在固定山门的服务(服务器进程)到分舵吃就行。

几个修真界里常见的踩坑

修真界里库这条路上有几颗常踩的雷。

第一颗雷,.so没加-fPIC。x86_64 上编译能过,运行时跳转到库代码时崩在 GOT 重定位上,报错是recompile with -fPIC。

第二颗雷,soname 设了但磁盘上的符号链接没建对。编译时-lfoo找不到libfoo.so,运行时DT_NEEDED=libfoo.so.1找不到libfoo.so.1。这种雷在跨机器部署(dev 编出来扔到 prod 跑)时高发。

第三颗雷,dlopen后没dlclose,长跑进程里.so累积在内存里导致 OOM。更隐蔽的情况是dlsym返回的函数指针被静态缓存,dlclose后函数指针变成野指针,下次调用崩。

第四颗雷,C++ 库没加extern "C"就被dlopen加载。C++ 函数有名字修饰(name mangling),dlsym(handle, "MyFunc")找不到符号。要么给库函数加extern "C",要么用c++filt反推。

第五颗雷,符号可见性没管,static函数漏到动态符号表。在 C 里static函数本来就是「本翻译单元内可见」,但全局变量没说static就默认 external linkage,进.so时就成了默认可见。教训:所有不想暴露的函数和全局变量,要么加static,要么靠-fvisibility=hidden兜底。

修真比喻:修真界这几颗雷,每一颗都让弟子写过不少 bug。-fPIC缺了是「铸剑没用好钢」,soname 漏了是「灵器没上户口」,句柄泄漏是「百宝囊不收口」,extern "C"漏了是「暗号没对上」,符号可见性漏了是「宗门密法泄到了江湖」。

修真界里这五颗雷踩中一颗,调试起来都痛苦。建议弟子每写一个库就拿nm -D、readelf -d、file这三个工具查一遍,三分钟的事能省后面三天。


修仙术语对照表

修仙术语技术现实本篇位置
装订器ar静态库归档工具静态库制作
印本典籍静态库.a,整段复制进可执行静态库制作
玉简传功动态库.so,运行时加载动态库制作
坐标重写-fPIC位置无关代码为什么需要 -fPIC
中转书页GOT(Global Offset Table)为什么需要 -fPIC
临阵拔刀dlopen运行时加载库dlopen / dlsym
暗号簿dlsym按名字查符号dlopen / dlsym
错误小纸条dlerror取错误信息dlopen / dlsym
临场收刀dlclose关闭库句柄dlopen / dlsym
传功规矩符号可见性 visibility符号可见性
外传功法目录__attribute__((visibility("default")))符号可见性
宗门密法不出声-fvisibility=hidden符号可见性
灵器身份证号soname(DT_SONAME)soname
门派代号soname 里的主版本号soname
真身real name 如libfoo.so.1.0.0soname
暗号linker name 如libfoo.sosoname
自带干粮静态部署,独立可执行静态 vs 动态
到分舵吃饭动态部署,运行时加载.so静态 vs 动态
铸剑缺钢.so没加-fPIC常见坑
灵器无户口soname 没建符号链接常见坑
百宝囊不收口dlopen后没dlclose常见坑
暗号没对上C++ 名字修饰与dlsym不匹配常见坑

进阶条件

  • 能用ar rcs打出一个静态库,并用ar t查看成员列表
  • 能用gcc -fPIC -shared打出一个动态库,并用file确认产物是 ELF shared object
  • 能解释为什么动态库必须加-fPIC,以及 GOT 在 PIC 里扮演什么角色
  • 能用dlopen+dlsym+dlclose+dlerror写一个简单的插件加载器
  • 能区分RTLD_LAZY和RTLD_NOW的差异,并说出一个推荐用RTLD_NOW的真实案例
  • 能用-fvisibility=hidden加白名单宏把一个库的导出符号收到只剩几个公开 API
  • 能解释 soname 与 real name、linker name 三者的关系,并画出从编译期到运行期的查找路径
  • 能用-Wl,-soname给.so设 soname,并正确建好符号链接让-lfoo编译能过、运行时ldd也能找到

修真界里这些条件全通过,库的制作和加载这一关就算破了。修真路不止,下一篇是 088 Makefile,看看弟子怎么把gcc -fPIC -shared -Wl,-soname,libmath.so.1 ...这一长串命令写成一个能自动跑的脚本。


下期预告 + 互动

下一篇:088 Makefile——把手动编译改成自动化脚本。弟子总不能每次都手敲一长串gcc -fPIC -shared -Wl,-soname,libmath.so.1 ...命令。Makefile 怎么写?变量怎么用?自动推导怎么省事?.PHONY是干啥的?GNU Make 的模式规则怎么配 CMake?下一篇把这些拆开看。

互动话题:

  1. 你修真路上踩过.so没加-fPIC的坑吗?报错信息是recompile with -fPIC还是别的?
  2. 你的项目里是用静态库多还是动态库多?选型的理由是什么?评论区等你。

落款

本文是「码农修仙传」系列第87篇。系列导航见 xren.ren

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询