静态链接含静态依赖的库时符号缺失问题及解决咨询
问题背景与错误
文件结构
test: { CMakeLists.txt, main.cpp, deps: { A: { CMakeLists.txt, include: { A.h }, src: { A.cpp }, deps: { B: { CMakeLists.txt, include: { B.h }, src: { B.cpp } } } } } }
各文件代码
main.cpp
#include <iostream> #include <A.h> int main() { std::cout << "hello: " << A::get() << std::endl; }
A.h
namespace A { int get(); }
A.cpp
#include "A.h" #include <B.h> int A::get() { return B::get(); }
B.h
namespace B { int get(); }
B.cpp
#include "B.h" int B::get() { return 7; }
test的CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(test LANGUAGES CXX) include(ExternalProject) add_executable(test main.cpp) ExternalProject_Add(project_A PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/A" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF ) add_dependencies(test project_A) target_link_libraries(test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A/lib/libA.a") target_include_directories(test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A/include")
A的CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(A LANGUAGES CXX) include(ExternalProject) add_library(A STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/A.cpp") target_include_directories(A PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") ExternalProject_Add(project_B PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/B" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF ) target_link_libraries(A PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/lib/libB.a") target_include_directories(A PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/include") add_dependencies(A project_B) install(TARGETS A) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION .)
B的CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(B LANGUAGES CXX) include(ExternalProject) add_library(B STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/B.cpp") target_include_directories(B PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") install(TARGETS B) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION .)
编译错误信息
At test root: cmake -S. -Bbuild ... cmake --build build [ 10%] Creating directories for 'project_A' [ 20%] No download step for 'project_A' [ 30%] No update step for 'project_A' [ 40%] No patch step for 'project_A' [ 50%] Performing configure step for 'project_A' -- The CXX compiler identification is AppleClang 15.0.0.15000309 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done (0.3s) -- Generating done (0.0s) -- Build files have been written to: /Documents/test/build/deps/A/src/project_A-build [ 60%] Performing build step for 'project_A' [ 10%] Creating directories for 'project_B' [ 20%] No download step for 'project_B' [ 30%] No update step for 'project_B' [ 40%] No patch step for 'project_B' [ 50%] Performing configure step for 'project_B' -- The CXX compiler identification is AppleClang 15.0.0.15000309 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done (0.2s) -- Generating done (0.0s) -- Build files have been written to: /Documents/test/deps/A/build/deps/B/src/project_B-build [ 60%] Performing build step for 'project_B' [ 50%] Building CXX object CMakeFiles/B.dir/src/B.cpp.o [100%] Linking CXX static library libB.a [100%] Built target B [ 70%] Performing install step for 'project_B' [100%] Built target B Install the project... -- Install configuration: "" -- Installing: /Documents/test/deps/A/build/deps/B/lib/libB.a -- Installing: /Documents/test/deps/A/build/deps/B/./include -- Installing: /Documents/test/deps/A/build/deps/B/./include/B.h [ 80%] Completed 'project_B' [ 80%] Built target project_B [ 90%] Building CXX object CMakeFiles/A.dir/src/A.cpp.o [100%] Linking CXX static library libA.a [100%] Built target A [ 70%] Performing install step for 'project_A' [ 80%] Built target project_B [100%] Built target A Install the project... -- Install configuration: "" -- Installing: /Documents/test/build/deps/A/lib/libA.a -- Installing: /Documents/test/build/deps/A/./include -- Installing: /Documents/test/build/deps/A/./include/A.h [ 80%] Completed 'project_A' [ 80%] Built target project_A [ 90%] Building CXX object CMakeFiles/test.dir/main.cpp.o [100%] Linking CXX executable test Undefined symbols for architecture arm64: "B::get()", referenced from: A::get() in libA.a[2](A.cpp.o) ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation) make[2]: *** [test] Error 1 make[1]: *** [CMakeFiles/test.dir/all] Error 2 make: *** [all] Error 2
错误原因
- 静态库的本质特性:静态库只是目标文件的归档集合,链接静态库时不会自动递归包含其依赖的其他静态库。A的代码调用了B的符号,但
libA.a中只包含A的目标文件,没有B的内容。 - 依赖传递配置错误:A的CMake中使用
target_link_libraries(A PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/lib/libB.a"),PRIVATE关键字意味着该依赖不会传递给链接A的上层目标(如test);同时静态库的链接操作不会将B的目标文件合并到A中。 - 安装过程未处理依赖:A的安装仅复制了自身的库和头文件,未将B的依赖信息或库文件传递给test项目,导致test链接时无法找到B的符号。
解决方案(无需test显式链接B)
以下两种方案可实现依赖逻辑封装在A中:
方案1:将B的目标文件合并到A静态库中
修改A的CMakeLists.txt,在构建完成后通过归档工具把libB.a的内容合并到libA.a中:
cmake_minimum_required(VERSION 3.5) project(A LANGUAGES CXX) include(ExternalProject) # 构建B ExternalProject_Add(project_B PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/B" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF ) # 构建A add_library(A STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/A.cpp") target_include_directories(A PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_include_directories(A PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/include") add_dependencies(A project_B) # 自定义命令:合并B到A add_custom_command(TARGET A POST_BUILD COMMAND ${CMAKE_AR} -x "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/lib/libB.a" COMMAND ${CMAKE_AR} -r $<TARGET_FILE:A> *.o COMMAND ${CMAKE_COMMAND} -E remove *.o WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Merging libB.a into libA.a" ) install(TARGETS A) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION .)
原理:通过ar -x解压libB.a得到目标文件,再用ar -r将这些文件添加到libA.a中,最终libA.a包含A和B的所有符号,test仅链接libA.a即可。
方案2:利用CMake配置文件自动传递依赖
此方案符合CMake现代最佳实践,通过导出依赖配置文件,让test自动获取A及其依赖B:
步骤1:修改B的CMakeLists.txt,导出配置文件
cmake_minimum_required(VERSION 3.5) project(B LANGUAGES CXX) add_library(B STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/B.cpp") target_include_directories(B PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") # 导出B的目标配置 install(TARGETS B EXPORT BTargets ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) install(EXPORT BTargets FILE BTargets.cmake DESTINATION lib/cmake/B) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION include) # 生成B的Config.cmake include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/BConfig.cmake" INSTALL_DESTINATION lib/cmake/B ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/BConfig.cmake" DESTINATION lib/cmake/B)
需在B的目录下创建空文件Config.cmake.in。
步骤2:修改A的CMakeLists.txt,传递依赖并导出自身配置
cmake_minimum_required(VERSION 3.5) project(A LANGUAGES CXX) include(ExternalProject) ExternalProject_Add(project_B PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/B" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF ) # 查找已安装的B find_package(B REQUIRED PATHS "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/B/lib/cmake/B") add_library(A STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/A.cpp") target_include_directories(A PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(A PUBLIC B) # 使用PUBLIC传递依赖给上层 add_dependencies(A project_B) # 导出A的目标配置 install(TARGETS A EXPORT ATargets ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) install(EXPORT ATargets FILE ATargets.cmake DESTINATION lib/cmake/A) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include" DESTINATION include) # 生成A的Config.cmake include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/AConfig.cmake" INSTALL_DESTINATION lib/cmake/A ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AConfig.cmake" DESTINATION lib/cmake/A)
需在A的目录下创建空文件Config.cmake.in。
步骤3:修改test的CMakeLists.txt,通过find_package引入A
cmake_minimum_required(VERSION 3.5) project(test LANGUAGES CXX) include(ExternalProject) ExternalProject_Add(project_A PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/A" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=OFF ) add_executable(test main.cpp) add_dependencies(test project_A) # 查找A的配置,自动获取A和B的依赖 find_package(A REQUIRED PATHS "${CMAKE_CURRENT_SOURCE_DIR}/build/deps/A/lib/cmake/A") target_link_libraries(test PRIVATE A)
原理:CMake通过配置文件自动解析A的依赖链,test只需链接A即可,无需知晓B的存在。
最佳实践
- 避免硬编码库路径:优先使用
find_package和CMake导出配置的方式管理依赖,而非直接指定静态库文件路径,提升项目可移植性。 - 合理使用链接关键字:静态库间的依赖若需传递给上层,应使用
PUBLIC或INTERFACE而非PRIVATE,确保依赖链正确传递。 - 优先采用现代CMake特性:利用
EXPORT、configure_package_config_file等特性实现依赖自动传递,这是CMake官方推荐的依赖管理方式。 - 静态库合并需谨慎:合并静态库会增大库体积,可能引入重复符号,仅在无法使用CMake依赖传递时采用。
内容的提问来源于stack exchange,提问作者luis
相关产品推荐
相关产品推荐

