在低版本Linux系统编译C++项目的若干经验
1)动态链接的优点、缺点是什么?
动态链接是现代C++开发的业界主流和默认实践。动态链接有如下优点:
第一,减小可执行文件的体积,因为可以动态链接库。
第二,可以减小运行时内存,因为多个进程可以共享内存中的同一个动态库。
第三,方便维护项目,更新库只需替换动态库文件,无需重新编译主程序。
缺点:
编译而成的可执行文件往往需要依赖glibc动态库,如果在高版本上编译,可执行文件依赖更高版本的glibc,如果将可执行文件部署在低版本Linux系统中,会报告
./myprogram: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by ./myprogram)
注:当然静态链接也有好处,即使在高版本系统静态链接的可执行文件,在低版本系统也能运行,不受glibc版本的限制,但是体积会大很多。
2)为了应对编译动态链接程序运行时可能出现的glibc依赖问题的解决办法。
在低版本Linux系统(比如CentOS6)编译可执行程序。低版本系统自带的glibc版本也低,当把依赖低版本glibc的可执行程序部署到高版本系统时,可以正常运行,因为glibc有向后兼容性。
3)低版本Linux系统(比如CentOS6)自带的编译工具链版本较低,而现代C++项目开发会使用到较新的C++语法特性。低版本编译工具链无法支持使用较高版本语法特性的C++项目的编译。具体体现在如下方面:
第一,编译器前端不理解新语法,简单地说,低版本编译器前端不认识一些新的词。
第二,标准库缺少新组件和特性。C++11 引入了 <chrono>等文件,低版本 GCC 的 include/c++ 目录里根本没有这些文件,编译时自然提示 fatal error: 'thread' file not found。即使是一些老牌头文件,如 <vector>,在 C++11 中也增加了新的成员函数,如 cbegin()、cend()、emplace_back()、shrink_to_fit()。低版本 GCC 的 <vector> 文件内部根本没有这些函数的实现代码,所以即使包含了头文件,调用这些方法时也会报错。
第三,GCC 的发布是编译器前端 + 标准库一起打包的。这是一个紧密耦合的整体:g++ 驱动负责编译和链接,而它使用的标准库 libstdc++.so 和头文件是与之匹配的版本。
4)如何解决低版本Linux系统自带的编译工具链版本较低的问题
笔者主要使用linux + centos6 编译C++项目,llvm在centos6上默认寻找gcc头文件,但是centos6自带的gcc工具链版本低,这里有2种解决方式
第一,使用devtoolset(Red Hat 为 RHEL 及其衍生系统CentOS专门提供的一个开发工具集,旨在解决系统自带编译器(如 GCC)版本过旧的问题)。它具有独立安装、不破坏系统、按需启用的优点。
第二,下载一个新版本的gcc,比如CentOS6编译的gcc8。
clang编译前,可以使用clang++ -v -x c++ -E /dev/null查看头文件搜索路径,在
#include "..." search starts here:
#include <...> search starts here:
后查看路径即可。
使用clang++ -print-search-dirs查看工具搜索路径(programs后的信息)、库文件搜索路径(libraries后的信息)。
如果头文件搜索路径、库文件搜索路径不正确,可以使用--gcc-toolchain,告诉Clang去指定的路径下寻找GCC安装目录,从而复用其中的库。使用-L指定libstdc++库所在路径,为什么需要指定2次呢?因为--gcc-toolchain主要针对的是编译,而-L针对链接时的搜索路径。
5)devtoolset8安装在centos6上,仍然使用了原始的libstdc++.so文件,为什么在链接、运行时没问题?
devtoolset8对应gcc8,理应使用更高版本的libstdc++.so文件,因为系统原始安装的gcc为4.4.7,对应的libstdc++.so文件,缺少gcc8中的部分特性,这里devtoolset8做了巧妙设计,首先devtoolset8安装的gcc8去链接了/opt/rh/devtoolset-8/root/usr/lib/gcc/x86_64-redhat-linux/8中的libstdc++.so,这是一个文本文件,里面的内容是有
INPUT ( /usr/lib64/libstdc++.so.6 -lstdc++_nonshared ),/usr/lib64/libstdc++.so.6仍然是原始系统中的libstdc++旧版本库文件,缺少的gcc8中的部分特性被放在libstdc++_nonshared.a中,最终的可执行文件会静态链接libstdc++_nonshared.a,保证了devtoolset8编译出的可执行文件只需要依赖原始系统的低版本libstdc++.so文件,而且libstdc++.so具有后向兼容性,即链接到高版本libstdc++.so,程序仍能正常运行,因为高版本libstdc++.so是低版本libstdc++.so的超集。原则上只要其依赖的glibc版本兼容,在一个系统上运行时如果报告GLIBCXX_xxx找不到,在系统的某个位置复制一个高版本libstdc++.so去链接就行,但是如果系统本身版本较低,自带的glibc版本较低,强行在链接、运行时使用一个更高版本的glibc库,则会出现各种奇怪的问题。
静态链接:链接的所有库都必须是静态库。
动态链接:链接的库可以有静态库、动态库。
6)编译可执行文件,动态链接的库,运行时找不到,如何处理?
使用-Wl,-rpath,'$ORIGIN/lib',其中$ORIGIN是一个特殊变量,代表可执行文件所在目录,在RPATH中使用它可以实现可移植性
编译器会将-Wl,-rpath,'$ORIGIN/lib'传给链接器
CMake设置RPATH的方式
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
使用如下命令查看动态链接的可执行文件的库搜索路径
readelf -d myapp | grep -E "RPATH|RUNPATH"
7)通过设置C_INCLUDE_PATH CPLUS_INCLUDE_PATH,可以指定编译C C++源文件时头文件搜索路径
8)redhat6默认没有安装glib-static,默认只有libc.so,没有libc.a,如果想在上面编译静态链接的可执行文件,最好使用root权限安装glib-static,否则只能在上面编译动态链接的可执行文件,就算从其他地方复制glib-static对应的静态库,也很麻烦,因为通常需要复制很多文件,比如libthread.a、libm.a等,版本需严格对应,不是官方推荐的方式,笔者使用这种方式,运行也遇到了很多奇怪的错误,所以不推荐链接复制的glibc库文件。
更多推荐


所有评论(0)