C++编译问题分类与最佳实践

C++编译问题分类与最佳实践

编译问题分类总结

背景

新规则:编译时禁止引入二进制文件,所有依赖库均需要基于源码编译(包括gcc本身)。

改造:基于以上规则,无法再采用目前的预编译方案,并且所有的第三方依赖库也不可采用预编译方式,任何依赖二进制的依赖库均需要改造为依赖源码。

总结了下,改造过程中遇到的编译问题可以分为如下6类:

  1. 代码同步问题。
  2. 工具链不兼容。
  3. 来自系统的依赖库不兼容。
  4. 依赖库编译选项不兼容。
  5. 依赖库多版本冲突。
  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)

解决方法:

  1. not effect: export ZLIB_DIR=./third_party/src/zlib/zlib/build/ (see find_package)
  2. not effect:: export ZLIB_ROOT=./third_party/installed/compression/ (see FindZLIB.cmake)
  3. OK: set cmake -DZLIB_ROOT=./third_party/installed/compression/
  4. 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。

编译最佳实践

  1. 确保编译流程自动化,避免穿插手动操作引入错误。
    1. 依赖库编译流程未自动化,这是一类比较严重的问题。很多依赖库lib.a是通过手动方式窜出来的,原始lib.a如何生成的、编译参数、依赖库、系统依赖库、编译顺序等都已不可知。
    2. 原则上应该禁止手动丢lib.a文件到预编译包中去的方式构建,因为其版本、其依赖库版本均未知且兼容性不可控,对于后续维护、解决版本冲突时带来极大困难。
  2. 确保依赖库版本明确指定,避免版本隐式变化时导致错误或潜在风险。
  3. 尽量统一依赖库版本,避免多版本冲突。
  4. 对于upstream仓库的代码,如果有修改需求,务必提交合并回去,避免自己维护特殊分支。
  5. 对于upstream仓库的cmake的依赖库版本定制,可以采取优雅的方式进行非侵入式的修改,包括传入编译选项、传入target、隐藏shadow冲突库。
  6. 对于自身项目开发,当add_library时,应该考虑到给外部暴露可定制化依赖库版本的方法,比如增加if build-lib-xxif not target lib-xx等判断。

<完>

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值