C++编译问题分类与最佳实践
编译问题分类总结
背景
新规则:编译时禁止引入二进制文件,所有依赖库均需要基于源码编译(包括gcc本身)。
改造:基于以上规则,无法再采用目前的预编译方案,并且所有的第三方依赖库也不可采用预编译方式,任何依赖二进制的依赖库均需要改造为依赖源码。
总结了下,改造过程中遇到的编译问题可以分为如下6类:
- 代码同步问题。
- 工具链不兼容。
- 来自系统的依赖库不兼容。
- 依赖库编译选项不兼容。
- 依赖库多版本冲突。
- 依赖库特殊问题。
代码同步问题
直接依赖库同步问题
Failed to clone 'third_party/submodule/lua' a second time, aborting
fatal: git fetch-pack: expected ACK/NAK, got 'ERR upload-pack: not our ref 98dbb9b8e46'
Fetched in submodule path 'third_party/submodule/brpc', but it did not contain 98dbb9b8e46.
Direct fetching of that commit failed.
解决方法:同步所有直接依赖库(约10个)。
间接依赖库同步问题
fatal: git fetch-pack: expected ACK/NAK, got 'ERR upload-pack: not our ref 98dbb9b'
fatal: clone of 'xx:third_party/msgpack.git' into submodule path '/third_party/submodule/xx/third-party/metrics/third/msgpack' failed
Failed to clone 'third/msgpack'. Retry scheduled
Failed to recurse into submodule path.
解决方法:同步所有间接依赖库(约20个),并且需要改造依赖的二进制文件为源码编译。
工具链不兼容
工具链版本不兼容
gcc、cmake、autoconf、automake、libtool。
- gcc version 6.3.0
- cmake version 3.13.4
- autoconf version 2.69
- automake version 1.16.1
- libtool version 2.4.6
cmake
cmake version 3.6.2 # scm
CMake Error at CMakeLists.txt:22
Unknown arguments specified VERSION_GREATER_EQUAL # supported by cmake 3.7.0
/usr/bin/cmake: /usr/lib/x86_64-linux-gnu/libcurl.so.4: no version information available (required by /usr/bin/cmake) # unknown currently
autoconf
Can't exec "autopoint": No such file or directory at /usr/share/autoconf/Autom4te/FileUtils.pm line 345.
autoreconf: failed to run autopoint: No such file or directory
autoreconf: autopoint is needed because this package uses Gettext
curl+cmake
Found *nroff option: -- -man
-- Looking for idn2_lookup_ul in idn2;dl;-pthread
-- Looking for idn2_lookup_ul in idn2;dl;-pthread - not found
-- Could NOT find LibSSH2 (missing: LIBSSH2_INCLUDE_DIR LIBSSH2_LIBRARY)
CMake Error at third_party/submodule/zxx/cpp/third_party/curl/CMakeLists.txt:709 (message):
CA path only supported by OpenSSL, GnuTLS or mbed TLS. Set
CURL_CA_PATH=none or enable one of those TLS backends.
解决方法:调试发现CURL_CA_PATH=none的情况下也报错,通过在外部设置set(CURL_CA_PATH_SET OFF)可解决。是个比较奇怪的问题,原因看起来像CURL_CA_PATH_SET默认值为TRUE,根源可能与cmake缓存有关。
gcc版本编译问题(高版本不兼容)
Using deprecated casting style. Use static_cast<int>
error: 'std::function' has not been declared
error: 'new' of type 'butil::ResourcePool<brpc::Socket>' with extended alignment 64 [-Werror=aligned-new=]
error: no matching constructor for initialization of 'std::lock_guard<std::mutex>' # mutable std::mutex
gcc版本编译问题(低版本不兼容)
error C++ versions less than C++14 are not supported.
protobuf: NameError: name 'licenses' is not defined
fcntl.h no such file or directory
sys/syscall.h no such file or directory
static_assert(bool, "") # gcc4 must take 2 args
struct designated initializer # gcc4 not support struct A{.a=1, .b=2}
gcc版本执行问题(二进制不兼容)
# upgrade gcc(libstdc++) or clang(libc++)
/usr/lib64/libstdc++.so.6: version 'GLIBCXX_3.4.22' not found
#strings /usr/lib64/libstdc++.so.6 | grep GLIBC
# upgrade glibc or downgrade gcc
/lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.28' not found (required by ./bin/myapp)
# recompile with lower version libc++, or config LD_LIBRARY_PATH to new version libc++(but may coredump)
/usr/lib/x86_64-linux-gnu/libstdc++.so.6: version 'CXXABI_1.3.11' not found (required by ./bin/myapp)
来自系统的依赖库不兼容
典型依赖库:openssl、zlib、libuwind、proto、bison。
gperf
libtcmalloc_minimal_internal.a && ar x "third_party/src/gperf/gperf/.libs/libtcmalloc_minimal_internal.a")
.libs/libstacktrace.a(stacktrace.o): In function 'GetStackTrace_libunwind(void**, int, int)':
stacktrace.cc:(.text+0x310): undefined reference to '_Ux86_64_getcontext'
解决方法:优先使用本地libunwind,避免链接/usr/lib/libunwind。其它依赖库也类似,否则每换一个操作系统都会引入类似的问题。
libuwind
libunwind.a(elf64.o): In function '_Uelf64_extract_minidebuginfo':
undefined reference to 'lzma_index_buffer_decode'
解决方法:disable liblzma(minidebuginfo)。
proto
#error This file was generated by an older version of protoc
解决方法:统一protoc版本。
bison
build thrift
checking for bison... yes
checking for bison version >= 2.5... no
configure: error: Bison version 2.5 or higher must be installed on the system!
解决方法:升级bison版本。
依赖库编译选项不兼容
libunwind
libunwind.a(Linit_local.o): relocation R_X86_64_PC32 against protected symbol '_ULx86_64_local_addr_space' can not be used when making a shared object
解决方法:避免使用jemalloc/build.sh,使用configure with --disable-shared。
thrift
/usr/bin/ld: cannot find -lthriftnb
解决方法:ensure make libevent before thrift, and add --with-libevent to thrift configure。
注意:zlib、openssl、libevent、thrift之间需要组合衔接上,并且需保持编译顺序,否则后续链接会失败或缺少某些特性等报错。
依赖库多版本冲突(源码冲突)
protobuf
#error incompatible with your Protocol Buffer headers.
fatal error: google/protobuf/port_def.inc: No such file or directory
google/protobuf/port_def.inc:74:2: error: #error PROTOBUF_DEPRECATED was previously defined
# maybe mixing different pb versions
解决方法:统一版本,若多个版本无法统一时,比如各个子依赖库带着多种版本protobuf(lib-a v3.2.0、lib-b v3.8.0),上游仓库lib-a适配为v3.8.0会报错,上游仓库lib-b适配为v3.2.0也会报错,只有折中适配为lib-a、lib-b都可兼容编译的v3.6.1(注意v3.7.0是一个坎)。
msgpack
add_library cannot create target 'msgpackc' because an imported target with the same name already exist
解决方法:优先使用依赖库中的msgpackc,无法避免冲突时,改造上游仓库采取屏蔽的方式。(这种冲突的核心原因是上游仓库写的不够严谨)。
gtest
gtest-internal-inl.h:125:40: error: 'Int32' has not been declared
解决方法:统一版本,无法统一版本时,隔离各个项目的gtest include配置。
依赖库多版本冲突(链接冲突)
bzip2
# after installed bzip2, may not be linked to target.
undefined reference to `BZ2_bzDecompress'
# lib-a link BZIP from lib installed on system.
# lib-b can't link BZIP due to not auto config.
undefined reference to `BZ2_bzDecompressInit'
解决方法:统一各个依赖库是否引入bzip2,避免编译选项串在一起引入莫名其妙的错误,或者彻底隔离2个依赖库对每个依赖库的编译选项。
zlib/gzip
# after fixed 'zlib.h: No such file or directory' by adding zlib .h/.a to installed
undefined reference to `google::protobuf::io::GzipOutputStream::Close()'
# maybe due to protobuf_WITH_ZLIB => find_package(ZLIB)
# exist /usr/share/cmake-3.7/Modules/FindZLIB.cmake
# error: sudo apt install zlib1g-dev: zlib1g-dev is already the newest version (1:1.2.8.dfsg-5+deb9u1)
解决方法:
- not effect: export ZLIB_DIR=./third_party/src/zlib/zlib/build/ (see find_package)
- not effect:: export ZLIB_ROOT=./third_party/installed/compression/ (see FindZLIB.cmake)
- OK: set cmake -DZLIB_ROOT=./third_party/installed/compression/
- todo: install cmake-zlib to /path/to/gcc/usr/lib/
thrift
# somewhere set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -lthrift -lthriftnb -lthriftz")
libthriftnb.so: undefined reference to 'evhttp_set_cb'
解决方法:避免cmake和-lsome-lib两种方式混用去导入库,而是统一使用cmake的target_link_libraries方式。
依赖库多版本冲突(运行时冲突)
rapidjson
# coredump
0x00007fa1802ea42a in __GI_abort () at abort.c:89
0x000055d4e9084cc6 in rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>::Clear
third_party/submodule/dbsc/third/infs/third/rapidjson/include/rapidjson/allocators.h:181
解决方法:统一版本。若要分析具体哪个依赖库导致coredump可借助ASan工具。
类似的典型冲突库还有spdlog、openssl等。
jemalloc
# coredump
0x00007fb46c16742a in __GI_abort () at abort.c:89
0x0000562e57e378c0 in je_free_default (ptr=0x560a55828c20) at src/jemalloc.c:2834
0x0000562e578d0114 in butil::demangle[abi:cxx11](char const*) (name=<optimized out>)
解决方法:低版本cmake对于链接参数–exclude-libs会在静态库之间传递,导致jemalloc静态链接时对glibc符号无法hook上。通过切断底层依赖库传递此参数可解决。
依赖库特殊问题
gtest
# source version conflicts
gtest Int32 not been declared
gmock IndexSequence not been declared
# bin version compatibility issues
libgtest.a(gtest-all.cc.o): relocation R_X86_64_32 against .rodata.str1.1 can not be used
解决方法:隔离各个依赖库的gtest。
boost
bootstrap.sh: line 195: ./tools/build/src/engine/build.sh: No such file or directory
Failed to build Boost.Build build engine
解决方法:同样的版本,有些网站下载的代码有问题,比如github下载,boostorg.jfrog.io下载的正常。
openssl
# compile errors with -j
make -j$PARALLEL
解决方法:去除-j参数。
tcmalloc、jemalloc
configure: error: C compiler cannot create executables
unwind_dir='Error! INPUT_UNWIND_PATH not exist'
解决方法:export INPUT_UNWIND_PATH
注意:内存管理库gperf/tcmalloc、jemalloc采用一个即可,避免混用;多线程环境下建议使用jemalloc。
编译最佳实践
- 确保编译流程自动化,避免穿插手动操作引入错误。
- 依赖库编译流程未自动化,这是一类比较严重的问题。很多依赖库lib.a是通过手动方式窜出来的,原始lib.a如何生成的、编译参数、依赖库、系统依赖库、编译顺序等都已不可知。
- 原则上应该禁止手动丢lib.a文件到预编译包中去的方式构建,因为其版本、其依赖库版本均未知且兼容性不可控,对于后续维护、解决版本冲突时带来极大困难。
- 确保依赖库版本明确指定,避免版本隐式变化时导致错误或潜在风险。
- 尽量统一依赖库版本,避免多版本冲突。
- 对于upstream仓库的代码,如果有修改需求,务必提交合并回去,避免自己维护特殊分支。
- 对于upstream仓库的cmake的依赖库版本定制,可以采取优雅的方式进行非侵入式的修改,包括传入编译选项、传入target、隐藏shadow冲突库。
- 对于自身项目开发,当add_library时,应该考虑到给外部暴露可定制化依赖库版本的方法,比如增加
if build-lib-xx
或if not target lib-xx
等判断。
<完>