ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux静态库与动态库完全指南:制作、链接与踩坑实战

Linux静态库与动态库完全指南:制作、链接与踩坑实战 1. 项目核心搞懂Linux里的静态库与动态库先抛个问题你在Linux下写C程序编译的时候是不是经常用-lm链接数学库那你知道这个m库究竟长什么样又是怎么被编译进去的吗其实libm.so和libm.a就是动态库和静态库的典型代表。这次我就拿自己实际折腾过的例子把Linux下动静态库的制作、使用、踩坑一次讲透。这个内容适合谁刚接触Linux开发的初学者、写了几年代码但一直在IDE里点按钮的程序员、还有想做嵌入式交叉编译却总被链接错误劝退的朋友。动静态库这个东西说白了就是别人写好的一堆函数的打包方式。你写项目的时候不需要重新实现字符串处理、网络协议、数学运算直接把库链进来就能调用。而怎么把库制作出来、怎么让别人能用、怎么避免链接时一堆undefined reference就是我这篇要聊的重点。我不打算讲那些教科书里已经写烂的定义而是用一套完整的实操路线带大家走一遍。我会先解释库的本质再分别演示静态库和动态库的制作过程最后把链接顺序、运行时找不到库、版本兼容这些高频问题一次性说清楚。整个过程用到的代码非常简单但背后的原理值得反复琢磨。2. 先别急着写代码搞明白库是什么东西2.1 库的本质就是一个代码仓库库本质上是一个或多个目标文件的打包集合。你在写C语言时每个.c源文件经过编译会得到一个.o目标文件。目标文件内部是机器码但还没有被链接成可执行文件。库的作用就是把一堆相关的.o文件捆绑在一起方便别人直接引用。生活化的类比是这样的你去餐厅吃饭不用自己从种菜、养猪开始而是直接点菜。库就是那个中央厨房已经把常用的菜预处理好了你只需要告诉服务员链接器你要什么菜。静态库相当于送到你家的半成品菜菜品已经打包好做菜时直接混进你的锅里之后跟餐厅再无关系。动态库则相当于餐厅后厨的现炒窗口你的菜做好的时候后厨帮你把菜直接加到盘子里但是后厨本身不跟你的盘子绑定——菜端走了后厨还在下次你可以再点别的菜。静态库在Windows下是.lib在Linux下是.a文件动态库在Windows下是.dll在Linux下是.so文件。这次我们只聊Linux设备上装个Ubuntu或CentOS就行。2.2 为什么会有两种库各解决什么问题静态库是早期最常用的形式。它的优点是部署简单可执行文件把库代码复制进了自己体内运行时不再依赖外部文件拷到别的机器上就能跑。缺点也很明显多个程序都静态链接同一个库那每个程序里都有一份库代码磁盘占用和内存占用成倍增长。比如你写了10个程序都链接到一个10MB的静态库那光这10个程序就占100MB磁盘运行起来内存里也有10份相同的代码。动态库就是为解决这个问题诞生的。程序编译时只记录我需要这个库里的哪些函数运行时再去系统指定的目录里找到.so文件加载进来。10个程序共享一个10MB的动态库磁盘上只有一份文件内存中也只需加载一份这就是了不得的节省。代价是产生了运行时依赖可执行文件需要能通过某种路径找到对应的.so文件找不到就报error while loading shared libraries。不少刚入门的朋友第一次碰到这个报错就很懵其实核心就是动态库的搜索路径没配好。静态库编译时打包进可执行文件运行时无依赖体积大。 动态库编译时只记录依赖运行时加载共享体积小但需要正确配置路径。2.3 制作库的前置工作源文件和编译环境这一步不复杂但容易出错。你需要准备几个源文件以及一个能用的编译环境。我通常用一个math_ops的迷你项目来做演示里面包含两个简单的数学操作函数一个加法一个乘法。/* add.c */ int add(int a, int b) { return a b; }/* mul.c */ int mul(int a, int b) { return a * b; }对应的头文件math_ops.h用于声明函数#ifndef MATH_OPS_H #define MATH_OPS_H int add(int a, int b); int mul(int a, int b); #endif这里有个关键点头文件里必须加#ifndef保护防止重复包含。虽然在这么简单的例子里看不出来但真实项目里头文件互相包含是很常见的不加保护就会报redefinition错误。检查编译环境的命令很简单gcc --version如果提示找不到gcc在Ubuntu系上执行sudo apt install build-essential在CentOS系上执行sudo yum groupinstall Development Tools。这一步不必多说只要你能编译helloworld环境就没问题。3. 从零开始制作并使用一个静态库3.1 核心操作就三条命令静态库的制作流程可以浓缩成三步编译成目标文件、用ar打包、生成索引可选但有价值。第一步把每个源文件编译成目标文件gcc -c add.c -o add.o gcc -c mul.c -o mul.o-c表示只编译不链接生成的是目标文件而不是可执行文件。很多人一开始会忽略-c结果报各种链接错误。注意这里没有加任何优化选项实际项目建议用-O2之类但制作库的原理是一样的。第二步用ar工具把目标文件打包成静态库。ar类似Windows下的zip但它的作用不是压缩而是把文件归档在一起ar rcs libmath_ops.a add.o mul.o参数解释如下r如果库中已有同名成员替换它。c创建库不输出提示信息。s在库中生成索引相当于给每顿饭做了一份菜单链接器查找时更快。libmath_ops.a是库文件名。Linux下静态库的命名约定是lib前缀加库名加.a后缀。库名叫math_ops文件就叫libmath_ops.a。如果你打破这个命名规则后面用-l选项链接时系统就找不到。第三步用nm或ar t验证库里面装了什么ar t libmath_ops.a输出会是add.o mul.o再试一下nm libmath_ops.a你能看到add和mul的符号信息。通过这步可以确认库没打错。3.2 写个测试程序把静态库链接进来库做出来不谈验证等于白做。写一个test.c#include stdio.h #include math_ops.h int main(void) { int x add(3, 4); int y mul(3, 4); printf(add(3,4)%d\n, x); printf(mul(3,4)%d\n, y); return 0; }编译时需要告诉编译器头文件在哪里、库文件在哪里gcc test.c -I. -L. -lmath_ops -o test_static这里面有几个参数新手很容易乱我拆开说-I.指定头文件搜索路径。.表示当前目录编译器默认只在标准目录找头文件如果你的头文件不在标准目录必须用-I指路。-L.指定库文件的搜索路径。同样链接器默认只在/lib、/usr/lib等目录找库。-lmath_ops指定要链接的库。去掉lib前缀和.a后缀剩下的就是库名。运行一下./test_static如果一切正常输出add(3,4)7 mul(3,4)12静态链接还有个验证技巧用file test_static看文件类型再用ldd看动态依赖。你会发现它只依赖libc等系统库完全没有libmath_ops的踪迹。说明add和mul的代码已经被复制进可执行文件了。3.3 一个必踩的坑ar命令的顺序和参数我第一次做静态库的时候卡在一个很诡异的问题上命令看起来完全正确但链接时/usr/bin/ld: cannot find -lmath_ops。后来发现是库文件名写错了。我把它命名成了math_ops.a而不是libmath_ops.a导致-lmath_ops找名字时根本对不上。这背后的规则是链接器遇到-lfoo会去找libfoo.so或libfoo.a而不是foo.a。这是一个约定俗成的规则除非你用-l:math_ops.a这种显式写法指定完整文件名GCC支持但不太常用否则老老实实按lib前缀命名。还有个坑是关于ar命令的。如果你先执行ar r libmath_ops.a add.o再执行ar r libmath_ops.a mul.o库是能生成的但不带索引某些情况下链接器可能无法定位符号。建议每次都带上s参数或者在ar之后执行ranlib libmath_ops.a两者作用相同都是为了重建库索引。在交叉编译环境中尤其需要注意目标平台的ar和本机的ar不能混用否则生成的库目标平台不认。3.4 静态库的优缺点这里给个打包结论优点部署方便运行时不需要考虑库文件的路径问题。在容器或嵌入式设备上减少运行时依赖是实打实的好处。我做过一个ARM交叉编译项目动态库依赖问题在板子上折腾了大半天换成静态库直接编译进可执行文件里问题就消失了。缺点每个程序都保存一份库代码空间浪费。另外静态库更新时所有链接过它的程序都必须重新编译。假如你给用户交付一个命令行工具用静态库省心至少不会出现我本地能跑服务器上找不到libxxx.so这种尴尬。4. 动态库的制作与链接重点在链接器的眼睛4.1 生成位置无关的代码PIC是关键动态库和静态库最大的编译差异在于动态库要求编译目标文件时加上-fPIC选项。PIC全称是Position Independent Code位置无关代码。为什么要这个因为动态库加载到内存的地址不固定每次运行时可能被装载到不同的虚拟地址。如果代码里到处是绝对地址那在一个地址编译好的代码换到另一个地址就废了。PIC会把地址访问改成相对寻址或通过全局偏移表GOT间接访问这样代码放在哪里都能跑。制作动态库的完整流程gcc -c -fPIC add.c -o add_pic.o gcc -c -fPIC mul.c -o mul_pic.o gcc -shared -fPIC -o libmath_ops.so add_pic.o mul_pic.o或者更简洁直接一条命令也行gcc -shared -fPIC -o libmath_ops.so add.c mul.c-shared表示生成共享库。注意这里也需要-fPIC因为即使源文件里没有PIC相关的写法编译器也要生成PIC代码。有些人认为只有-c时需要-fPIC链接时不需要加了也不冲突。稳妥起见两个阶段都加上。有了.so之后可以用readelf -d libmath_ops.so查看它的动态段信息能看到SONAME、依赖库等元数据。也可以直接nm -D libmath_ops.so查看动态导出符号。4.2 编译并运行动态库链接的程序编译命令和静态库类似gcc test.c -I. -L. -lmath_ops -o test_dynamic运行它./test_dynamic此时大概率会出问题error while loading shared libraries: libmath_ops.so: cannot open shared object file: No such file or directory很多新手以为明明是当前目录下有这个文件为什么找不到。原因是动态链接器的搜索路径里不包含当前目录出于安全考虑。Linux下默认搜索路径是/lib、/usr/lib以及/etc/ld.so.conf里配置的目录。当前目录.不在其中。解决办法有三种把.so文件拷贝到/usr/lib或/usr/local/lib然后运行sudo ldconfig更新缓存。这适合正式安装场景。设置临时环境变量export LD_LIBRARY_PATH.:$LD_LIBRARY_PATH再运行程序。这适合开发调试。编译时指定rpath把搜索路径写进可执行文件里gcc test.c -I. -L. -lmath_ops -Wl,-rpath,$ORIGIN -o test_dynamic$ORIGIN表示可执行文件所在目录这样程序运行时会优先在当前目录找库。我个人的开发习惯是第二种用完就取消环境变量避免污染全局。而正式部署我通常把库放到专门的目录比如/opt/mylib然后在/etc/ld.so.conf.d/下添加一个.conf文件写上一行目录路径再执行ldconfig。这样既规范又不需要把库混入系统目录。4.3 小心一个容易忽略的问题链接时用了动态库运行时也要能找到它这是一个很多人栽过跟头的事编译通过了运行时却报找不到库。原因前面已经说了编译和运行是两回事。编译时链接器根据-L指定的路径找到.so运行时动态链接器根据/lib、/usr/lib和LD_LIBRARY_PATH等路径再次寻找同一个.so。如果编译机器上有库、运行机器上没有就出现开发环境万事大吉测试环境立刻崩的现象。我通常用一条命令来检查可执行文件搜索库的路径ldd test_dynamic如果libmath_ops.so这一行显示not found那就说明运行时的路径没配对。这条命令是个宝几乎所有动态库问题都能通过它先定位。4.4 用ldd和readelf验证依赖readelf -d test_dynamic可以看到NEEDED段里面有libmath_ops.so。ldd则会递归解析所有依赖并输出实际找到的路径。这两个工具是我每次排查动态库问题时最先执行的。有一次我在交叉编译环境里发现程序放到板子上运行就报找不到libstdc.so.6用ldd一看依赖列表里指向的是PC上的绝对路径/usr/lib/x86_64-linux-gnu/libstdc.so.6。这种情况就是因为交叉编译时没有设置好SYSROOT导致链接器把本机的库路径当成了目标板的路径。后续在工具链里正确指定了--sysroot问题才算真正根治。5. 实操对比静态库与动态库的选型逻辑5.1 大小对比先看实际数据我做了个小实验分别用静态库和动态库编译同一个test.cls -lh test_static test_dynamic libmath_ops.a libmath_ops.so我的机器上大概结果如下因gcc版本不同会有些差异-rw-r--r-- 1 root root 1560 libmath_ops.a -rwxr-xr-x 1 root root 7528 libmath_ops.so -rwxr-xr-x 1 root root 15848 test_static -rwxr-xr-x 1 root root 15856 test_dynamic等一下静态版怎么跟动态版差不多大这可能是因为这个测试程序太简单而系统默认链接的glibc等库已经占了很大比重。换个复杂一点的程序比如链接一个几十MB的第三方库差距就体现出来了。所以这个对比不是绝对的在不同项目里结果不一样。但有一个点是确定的静态库的可执行文件里包含了库代码动态库则不包含。所以如果10个程序都用静态库磁盘上会有10份库代码的拷贝用动态库只有一份。内存同理。如果你的程序是常驻后台的服务进程动静态的差异在内存占用上很值得考量。5.2 编译时间的差异动态库修改后如果保持接口不变只需要重新生成.so链接了它但未重新编译的可执行文件仍然可以运行前提是运行时能找到并加载新.so。静态库修改后所有依赖它的程序都必须重新编译链接因为旧的可执行文件里拷贝的是旧代码。这在大型项目里影响很大。比如我们组有个公共日志组件最初是静态库。每次修复一个日志模块的bug业务方都要重新编译整个应用。后来我把日志组件改成动态库业务方只需要更新liblog_xxx.so程序重启后就会加载新库省了大量重新编译的时间。当然这种操作的前提是接口没有变否则会出现符号找不到或行为异常的问题。5.3 什么时候必须用静态库什么时候优先动态库必须用静态库的场景有几种目标运行环境极简连基本的动态库都不全。常见于嵌入式Linux或精简容器里。需要对所有依赖完全掌控不希望在发布后因为系统的某个库升级导致程序崩溃。程序是单个可执行文件想做到拷走就能跑。优先用动态库的场景多个项目共享同一个库且更新频繁。动态库避免重复编译也节省内存。希望能在不替换二进制文件的情况下比如插件体系动态加载功能。通过dlopen和dlsym你甚至可以在运行时按需加载不同的.so文件这是动态库区别于静态库的独有优势。有一种中间做法是混合链接大部分核心逻辑用静态库系统级或第三方大库用动态库。比如我用静态库链接自己的业务代码用动态库链接OpenSSL之类系统提供的库。这样既保住了业务发布时的稳定性又避免把整个系统库都打进来。6. 动静态库制作中的常见问题与排查技巧6.1 链接时报 undefined reference怎么办undefined reference to xxx是遇到最多的错误。它发生在链接阶段意味着链接器在所有指定的库和目标文件里都没找到xxx这个符号的定义。常见原因库的链接顺序不对。这是经典问题。以静态库为例链接器是从左到右扫描的如果一个.a库引用了后面库的符号而后面库还没被扫描到就会报 undefined。解决办法是把依赖库放在引用它的目标文件后面。gcc main.o -lmath_ops -o app # 正确 gcc -lmath_ops main.o -o app # 可能失败因为math_ops的符号解析时main.o尚未处理实际规则是说链接器维护一个待解析符号集合每处理一个.o或.a文件就把已定义的符号放进去同时收集未解析的符号。如果一个.a文件在遇到main.o之前就被处理了它里面的add和mul可能根本没有被提取因为链接器不知道后续会用到它们。这导致整个链接失败。把main.o放在-lmath_ops前面就绕开了这个问题。至于动态库顺序问题相对宽松一些但也建议按依赖方向排序避免不必要的警告。忘记链接指定的库。比如用到了sin、cos却忘了-lm。在较老的gcc版本中数学函数并不在默认链接的libc里而是单独的libm必须显式加-lm。新版gcc的glibc行为有变化但-lm始终是安全的。库文件编译时用了C但你的程序是C语言且库代码里没有extern C。C有函数重载符号会被mangled变成_Z3addii之类C程序自然找不到。解决方案是在库的头文件里加上#ifdef __cplusplus extern C { #endif int add(int a, int b); #ifdef __cplusplus } #endif6.2 动态库版本管理SONAME、符号版本和兼容性动态库不是一锤子买卖涉及到升级维护。你写了一个libfoo.so.1.0.0后来又出了1.1.0。如果程序在编译链接时记下的绝对是libfoo.so.1.0.0那升级库文件后程序就找不到原来的库了。为此Linux引入了SONAME机制。在生成动态库时你可以用-Wl,-soname,libfoo.so.1指定SONAME。这样可执行文件里记录的是libfoo.so.1这个逻辑名而不是具体的libfoo.so.1.0.0。系统里再创建软链接libfoo.so - libfoo.so.1 libfoo.so.1 - libfoo.so.1.0.0当库升级到libfoo.so.1.1.0时只需要把libfoo.so.1软链接指向新版本运行时程序加载的仍然是libfoo.so.1不需要重新编译。但如果接口发生了不兼容变化SONAME应该改成libfoo.so.2防止旧程序自动加载不兼容的新库。这个就叫ABI兼容性管理。制作带SONAME的动态库gcc -shared -fPIC -Wl,-soname,libmath_ops.so.1 -o libmath_ops.so.1.0 add_pic.o mul_pic.o ln -s libmath_ops.so.1.0 libmath_ops.so.1 ln -s libmath_ops.so.1 libmath_ops.so编译链接时gcc test.c -I. -L. -lmath_ops -o test_dynamic运行前确保libmath_ops.so.1能被找到可以用LD_LIBRARY_PATH指到当前目录。这些细节也许现在做简单实验时用不上但一旦进入正式产品或开源项目维护SONAME就是硬约束。我在参与一个开源项目时维护者明确要求每次不兼容改动都必须改SONAME不然用户的发行版系统会把所有依赖旧接口的程序全部弄得无法运行。6.3 使用dlopen在运行时按需加载动态库动态库还有一个静态库给不了的能力程序运行期间加载和卸载库。通过系统提供的dlopen和dlsym你可以在不需要库的时候不加载需要时再打开甚至实现插件架构。一个简化的例子#include stdio.h #include dlfcn.h int main(void) { void *handle dlopen(./libmath_ops.so, RTLD_LAZY); if (!handle) { fprintf(stderr, dlopen error: %s\n, dlerror()); return 1; } int (*add_func)(int, int) (int (*)(int, int))dlsym(handle, add); if (!add_func) { fprintf(stderr, dlsym error: %s\n, dlerror()); return 1; } printf(dlsym add(5,6)%d\n, add_func(5, 6)); dlclose(handle); return 0; }编译时gcc test_dl.c -ldl -o test_dl注意这个test_dl编译时并不直接依赖libmath_ops.so而是在运行的时候才通过dlopen加载。这种机制特别适合插件系统。比如一个图像处理软件每个滤镜都是一个.so主程序启动时扫描插件目录能用dlopen加载就加载不用就忽略。添加新滤镜时完全不需要重新编译主程序。这个思路也常用于热更新场景库文件替换后程序定期dlclose再dlopen就加载了新代码。但要注意线程安全库被卸载时如果还有线程在跑其中的函数会出现segment fault。稳妥的做法是保证卸载时没有线程在使用库内部函数。6.4 排查技巧速查表我把实际中最常遇到的问题整理成一张表方便大家对照排查现象可能原因检查方式编译时报cannot find -lxxx库文件不存在、命名不符合libxxx.a/so规范、-L路径不对用find / -name libxxx*或echo $LIBRARY_PATH运行时找不到.so动态库搜索路径未包含该.so所在目录用ldd查看设置LD_LIBRARY_PATHundefined reference to链接顺序错误、没加对应库、库是用C编译但无extern C按依赖顺序排列-l参数查看nm符号程序装载时报version GLIBCXX not found编译环境与运行环境的C运行时库版本不一致更新目标机的libstdc.so.6或使用静态链接编译工具链多个.so导出同名全局函数导致调用错乱动态库符号冲突用-fvisibilityhidden隐藏非必要符号或显式控制dlopen时的RTLD_LOCAL-fvisibilityhidden是个高级用法值得多提一句。默认情况下编译动态库时所有的非static函数都会导出符号这意味着只要知道符号名外部程序可以dlsym调用你不想公开的函数。为了安全和你自己的架构洁净很多项目会在编译动态库时加上gcc -shared -fPIC -fvisibilityhidden -o libfoo.so source.c然后在要导出的函数声明前加上__attribute__((visibility(default))) int public_function(void);这样只有标记过的函数才会被导出其他函数默认隐藏。这个做法在大型项目里非常常见既能减少符号冲突又能降低动态库对外暴露的接口面积。6.5 一个真实的排查案例我在处理一个内部工具时编译时一切正常但运行时提示/usr/local/lib/libfoo.so: undefined symbol: bar。第一次遇到时我满脑子问号一个.so文件都加载进来了怎么会找不到符号后来用nm -D /usr/local/lib/libfoo.so | grep bar发现bar符号是U开头的表示它需要外部提供但程序本身没链接包含bar的库。这个bar函数其实来自另一个动态库libbaz.so。原来libfoo.so的作者在编译时只声明了bar但忘了链接libbaz.so导致依赖信息缺失。最后的解决办法有两个一是重新编译libfoo.so在链接时加上-lbaz让依赖完整地写入libfoo.so的NEEDED段二是在可执行程序编译时手动加-lbaz。第一种更规范因为依赖应该由库自己声明而不是让使用方去猜。这个案例告诉我ldd查的是当前可执行文件的整体依赖如果你想知道某个.so自身依赖了哪些库可以用readelf -d libfoo.so | grep NEEDED这是排查符号缺失问题时的第一手信息。7. 动手项目一个简单的插件式计算器前面讲了不少原理和命令这里我分享一个刚兴趣时做过的小项目大家可以直接抄作业。目标是做一个计算器主程序支持加载不同的数学操作插件。每个插件是一个.so声明自己的名称和运算函数。先定义插件接口头文件plugin.h#ifndef PLUGIN_H #define PLUGIN_H typedef struct plugin { const char *name; int (*op)(int, int); } plugin_t; #endif写一个加法插件add_plugin.c#include plugin.h int add(int a, int b) { return a b; } plugin_t add_plugin { .name add, .op add, };编译成动态库gcc -shared -fPIC -o add_plugin.so add_plugin.c另一个乘法插件mul_plugin.c类似函数变成mul。主程序main.c用dlopen加载插件目录下所有.so然后调用对应接口#include stdio.h #include dirent.h #include dlfcn.h #include string.h #include plugin.h int main(void) { struct dirent *entry; DIR *dir opendir(./plugins); if (!dir) { perror(opendir); return 1; } while ((entry readdir(dir)) ! NULL) { if (strstr(entry-d_name, .so) NULL) { continue; } char path[256]; snprintf(path, sizeof(path), ./plugins/%s, entry-d_name); void *handle dlopen(path, RTLD_LAZY); if (!handle) { fprintf(stderr, dlopen %s failed: %s\n, path, dlerror()); continue; } plugin_t *p (plugin_t *)dlsym(handle, add_plugin); plugin_t *m (plugin_t *)dlsym(handle, mul_plugin); if (p) { printf(插件 %s 结果: %d\n, p-name, p-op(10, 20)); } else if (m) { printf(插件 %s 结果: %d\n, m-name, m-op(10, 20)); } dlclose(handle); } closedir(dir); return 0; }我把两个插件的符号处理写得比较粗糙实际项目应该更规范地约定导出函数名比如统一导出plugin_entry函数。但整个思路就是这么回事主程序只需要知道接口约定不需要关心每个插件的实现细节。新增一个减法插件只需把sub_plugin.so丢进目录主程序不用重新编译。这个项目的实战意义在于动静态库不仅是把函数打包给别人用更是一种软件架构工具。静态库适合把所有逻辑绑死在一起动态库则天然支持解耦和插件化。8. 最后踩坑后的几句忠告动静态库的制作和链接看似只是几条gcc命令但其背后是Linux程序构建体系的核心。我在实践中深有体会只要掌握了两条原则大部分问题都能迎刃而解。第一条编译期和运行期是两回事。编译期链接器按你给的路径找库运行期动态链接器按系统的搜索规则找库两个阶段可以完全不一样。第二条链接顺序就是符号解析顺序从左到右扫描把被依赖的库放在后面依赖别人的库放在前面。最后再分享一个小技巧。排查库问题时不要急着apt install或yum install先看看自己的PATH、LD_LIBRARY_PATH、LIBRARY_PATH是不是被改乱了。很多人装了一些环境管理工具后这些变量变得一团糟编译时链接到A版本的库运行时却加载了B版本的库就会出现各种莫名其妙的行为。用echo $LD_LIBRARY_PATH检查一下往往能帮你省下半天时间。多折腾几次你就不会觉得库是个黑盒了。
返回列表