Linux C++生成静态库与动态库

之前一篇文章讲了C++编译过程,经过前面的分析,我们知道C++代码走完编译全流程,将汇编文件*.o通过ld指令链接运行库CRT,就可以生成可执行程序,当然这有一个前提是源码中有默认入口函数main或者ld -E指定一个入口函数。

与生成可执行文件类似,静态库与动态库也要经过编译的前三步,将源码翻译成汇编文件*.o,只是不执行最后一步的ld链接指令。

静态库生成

静态库生成就像压缩打包,用ar工具来生成:

ar cr libhelloworld.a [xxx.o]

# -c 如果存档文件不存在,则创建。
# -r 向存档文件中插入.o文件,替换已有的同名文件,新成员添加到文档末尾。
# 可以通过ar t xxx.a查看已有的归档内容。

ar工具有一个妙用:当你依赖的第三方库比较多,比如grpc工程、boost库、abseil等,可以在生成静态库的时候,把所有*.o中间文件全部打包到一个libxxx.a静态库中,这样编译的时候就不需要每一个静态库都在编译脚本中写一遍。打包的参考命令:

find ./ -name "*.o" | xargs ar cr libxxx.a

静态库生成的二进制部署非常方便。在实际工作中,我参与过的几个业务,也是都使用静态库,所有代码在一个大仓下面,通过bazel的BUILD来处理编译依赖。

静态链接的坑

静态库需要静态链接,静态链接通常的指令:

g++ main.cpp -s -static ./libxxx.a

在实际工程中使用静态链接要小心,openssl的Configure选项对-static有专门的描述,总结起来就是因为存在getaddrinfo和gethostbyname的调用,建议在生产环境不要用-static来消除glibc动态库的依赖。为了让大家注意到这个点,编译器会产生如下告警:

../libopenssl/3rdlib/openssl/lib/libcrypto.a(b_sock.o): In function `BIO_gethostbyname':
b_sock.c:(.text+0x71): warning: Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

这个告警是说gethostbyname静态链接时一定会链接glibc的动态库版本,类似的函数还有dlopen、getaddrinfo、getpwnam、getpwuid、endpwent、getservbyname。

为什么会有告警呢,因为上面的函数即使采用-static静态链接,运行时也需要dlopen glibc。这种行为是未知的,可能因为glibc环境不同导致崩溃。

gethostbyname是万恶之源,静态链接在多个线程中都调用到gethostbyname函数,程序就必然崩溃,也无法捕获异常,加锁也不行,多线程中静态链接gethostbyname和getaddrinfo也会crash。
可以通过以下参数检查是否依赖glibc,非静态链接就会有依赖的符号,而静态链接则没有:

# nm a.out |grep GLIBC_

nm: a.out: no symbols 表示没有glibc的依赖

那么既然只有几个函数有这样的问题,能不能找出那几个函数对应的glibc库,不对其进行静态链接呢?ld的确提供了这样的方法,比如:

g++ main.cpp -Bstatic -lm -Bdynamic -ldl

只不过这样做太麻烦了。实际上glibc在每台机器上都必须有,所以只需要静态链接c++的库就可以满足很多跨机拷贝执行的需求:

g++ main.cpp -s -static-libstdc++ -static-libgcc

不过glibc版本只向下兼容,如果在高版本的机器上编译,就不能在低版本的机器上运行:

/lib64/libc.so.6: version `GLIBC_2.25' not found (required by ./a.out)

此时就需要自己来判断是否使用-static选项了,建议不要在生产环境使用。生产环境也可以选择:
1、容器发布
2、打包SO文件
3、glibc多版本共存


动态库生成

尽管我不使用动态库,但是据我所知也有不少团队使用动态库插件,以便于热更新。比如国内某头部云厂商的主机安全agent就是动态库方式。相信还有很多C端的程序,也偏向于使用动态库。
动态库使用g++指令生成:

g++ -shared -fPIC -o libhelloworld.so [xxx.o]

# -share该选项指定生成动态连接库。
# -fPIC表示编译为位置独立的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

生成动态库时链接静态库要小心,尤其是当出现如下告警时:

relocation R_X86_64_PC32 against symbol 'ares_free' can not be used when making a shared object; recompile with -fPIC

这个时候如果根据错误提示,贸然的将链接的静态库加上-fPIC,可能会导致段错误。推荐的做法是链接的库也改成动态库。

其实,动态库和静态库,没有绝对的优劣势,重要的是要看是否适合自己业务场景。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值