前言
每一个今天你绕过去不填的坑,都会在未来等着你。
—哲·士沃硕德
正文
一个C/C++程序从源码到可执行文件都需要经过 预处理-编译-汇编-链接 这几个过程,当然现在只需要gcc x.c
就可以了,而不需要我们去执行具体的cpp
等程序了,非常的方便。
回到今天的case上来,当我们需要编写一个依赖第三方库的程序时,该如何gcc x.c
呢?
以Redis的C客户端hiredis为例,让我们看一下它的Makefile是怎控制编译链接的。
INSTALL?= cp -a
$(PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@
@echo Name: hiredis >> $@
@echo Description: Minimalistic C client library for Redis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
可以看出,当我们执行make install
时,将头文件复制到了指定的include目录,将so文件放到的lib下,并且生成了软链接,更新了pkgconfig。
这是非常标准的做法了,因此现在我们的libhireids其实和libstdc++差不多了。都放在了同一位置。
但如果我们不能执行make install,或者说只编译出了so文件,要怎么手动链接使用这个库呢?
首先需要头文件,这个很好解决,在编译时通过指定-I
参数,设置头文件寻找的路径。
比较麻烦的是链接动态库。在编译时通过指定-L
参数,设置动态库寻找的路径。
但这样编译不报错误就完了吗?就可以成功执行了吗?
直接执行,往往会报这样错误
./a.out: error while loading shared libraries: libhiredis.so.0.13: cannot open shared object file: No such file or directory
ldd
命令可以查看可执行文件依赖了哪些库
libhiredis.so.0.13 => not found
可以看到,似乎并没有链接成功啊
这是为啥呢,如何才能链接成功呢?
之前在学校遇到这种情况,我会将路径添加到/etc/ldconfig.so.conf ,再执行ldconfig刷新配置,就可以成功链接了。虽然不懂为啥,但一直可以使用,结果这次在服务器上并没有sudo权限,所以也不能执行这样的操作了。
先说解决方案,在编译时添加参数-Wl,-rpath=/xx/hiredis/
,就可以成功了。
再说为啥,这个参数-Wl,-rpath=
是gcc传递给链接器的,我对装载链接也不是很懂,但现在的理解就是,-L
参数,是在编译时告诉链接器动态链接库的路径,-Wl,-rpath=
则告诉了链接器程序运行时,动态链接库的路径,当然,这两个路径应该是一样的。
我们之前修改ldconfig的配置文件,实际上是指定了全局的动态链接库搜索路径,这应该保证了编译和运行时我们都不需要再指定路径了(存疑,没有测试)。
而-Wl,-rpath=
和-L
都是指定了本次的路径。
tips
那么makefile中,为什么还要添加软链接呢?
我们可以看到,动态库都是有版本号的,如上面的libhiredis.so.0.13,但是我们在链接库时,不会告诉编译器,我要链接libhiredis.so.0.13,这样以后动态库升级版本了,所有的makefile都要跟着更新版本,非常的麻烦。
而建一个软链,名字统一为libhiredis.so,makefile就链接这个文件名,而之后版本升级,并不需要改makefile,只修改链接关系就可以了。
嗯,这其实就相当于做了一层封装~
参考阅读
https://stackoverflow.com/questions/8482152/whats-the-difference-between-rpath-and-l
https://stackoverflow.com/questions/6562403/i-dont-understand-wl-rpath-wl