双笙子佯谬老师的【公开课】现代CMake高级教程课程笔记
第 3 章:链接库文件
main.cpp 调用 mylib.cpp 里的 say_hello 函数
$tree
.
├── CMakeLists.txt
├── main.cpp
├── mylib.cpp
└── mylib.h
// main.cpp
#include "mylib.h"
int main() {
say_hello();
}
// mylib.h
#pragma once
void say_hello();
// mylib.cpp
#include "mylib.h"
#include <cstdio>
void say_hello()
{
printf("hello, mylib!\n");
}
1. 直接链接到一起编译
# CMakeLists.txt
add_executable(main main.cpp mylib.cpp)
2. mylib 作为一个静态库
add_library(mylib STATIC mylib.cpp)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)
编译:
$cmake --build build
[build] [1/3 33% :: 0.194] Building CXX object CMakeFiles/mylib.dir/mylib.cpp.o
[build] [2/3 66% :: 0.420] Linking CXX static library libmylib.a
[build] [3/3 100% :: 0.741] Linking CXX executable main
[build] Build finished with exit code 0
生成了 libmylib.a:
$ls build
CMakeCache.txt CMakeFiles build.ninja cmake_install.cmake compile_commands.json libmylib.a main mylib
3. mylib 作为一个动态库
add_library(mylib SHARED mylib.cpp)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)
编译:
[main] Building folder: CMakeLession
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /mnt/h/Code/lessonCode/CMakeLession/build --config Debug --target all --
[build] [1/3 33% :: 0.485] Building CXX object CMakeFiles/mylib.dir/mylib.cpp.o
[build] [2/3 66% :: 0.786] Linking CXX shared library libmylib.so
[build] [3/3 100% :: 1.071] Linking CXX executable main
[build] Build finished with exit code 0
[main] Building folder: CMakeLession
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /mnt/h/Code/lessonCode/CMakeLession/build --config Debug --target all --
[build] [1/3 33% :: 0.485] Building CXX object CMakeFiles/mylib.dir/mylib.cpp.o
[build] [2/3 66% :: 0.786] Linking CXX shared library libmylib.so
[build] [3/3 100% :: 1.071] Linking CXX executable main
[build] Build finished with exit code 0
4. mylib 作为一个对象库
对象库类似于静态库,但不生成 .a 文件,只由 CMake 记住该库生成了哪些对象文件
add_library(mylib OBJECT mylib.cpp)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)
编译:
cmake --build build
[ 33%] Building CXX object CMakeFiles/mylib.dir/mylib.cpp.o
[ 33%] Built target mylib
[ 66%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
对象库类似于静态库,但不生成 .a 文件,只由 CMake 记住该库生成了哪些对象文件,对象库是 CMake 自创的,绕开了编译器和操作系统的各种繁琐规则,保证了跨平台统一性。在自己的项目中,我推荐全部用对象库(OBJECT)替代静态库(STATIC)避免跨平台的麻烦。
对象库仅仅作为组织代码的方式,而实际生成的可执行文件只有一个,减轻了部署的困难。
❯ ls build/CMakeFiles/mylib.dir
DependInfo.cmake build.make cmake_clean.cmake compiler_depend.make compiler_depend.ts depend.make flags.make mylib.cpp.o mylib.cpp.o.d progress.make
静态库的麻烦:GCC 编译器自作聪明,会自动剔除没有引用符号的那些对象
例:
CMakeLists.txt
add_library(mylib STATIC mylib.cpp)
mylib.cpp
#include <cstdio>
// 此处进行静态初始化
static int unused = printf("mylib initialized!");
main.cpp
#include <cstdio>
int main()
{
printf("main function\n");
}
编译:
❯ cmake --build build
[ 50%] Built target mylib
Consolidate compiler generated dependencies of target main
[ 75%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
查看符号:
.init_array:0000000000003DB8 _init_array segment qword public 'DATA' use64
.init_array:0000000000003DB8 assume cs:_init_array
.init_array:0000000000003DB8 ;org 3DB8h
.init_array:0000000000003DB8 __frame_dummy_init_array_entry dq offset frame_dummy
.init_array:0000000000003DB8 ; DATA XREF: LOAD:0000000000000168↑o
.init_array:0000000000003DB8 ; LOAD:00000000000002F0↑o
.init_array:0000000000003DB8 _init_array ends
这里可以看到没有 mylib 的初始化
对象库可以绕开编译器的不统一:保证不会自动剔除没引用到的对象文件。我们改成 OBJECT:
CMakeLists.txt
add_library(mylib STATIC mylib.cpp)
使用 ida 可以看到 mylib 初始化:
.init_array:0000000000003DA8 _init_array segment qword public 'DATA' use64
.init_array:0000000000003DA8 assume cs:_init_array
.init_array:0000000000003DA8 ;org 3DA8h
.init_array:0000000000003DA8 __frame_dummy_init_array_entry dq offset frame_dummy
.init_array:0000000000003DA8 ; DATA XREF: LOAD:0000000000000168↑o
.init_array:0000000000003DA8 ; LOAD:00000000000002F0↑o
.init_array:0000000000003DB0 dq offset _GLOBAL__sub_I_mylib_cpp
.init_array:0000000000003DB0 _init_array ends
虽然动态库也可以避免剔除没引用的对象文件,但引入了运行时链接的麻烦
小技巧
add_library 无参数
add_library 无参数时,是静态库还是动态库?
会根据 BUILD_SHARED_LIBS 这个变量的值决定是动态库还是静态库。ON 则相当于 SHARED,OFF 则相当于 STATIC。如果未指定 BUILD_SHARED_LIBS 变量,则默认为 STATIC。
因此,如果发现一个项目里的 add_library 都是无参数的,意味着你可以用:cmake -B build -DBUILD_SHARED_LIBS:BOOL=ON
来让他全部生成为动态库。稍后会详解命令行传递变量的规则。
设定一个变量的默认值
要让 BUILD_SHARED_LIBS 默认为 ON,可以用下图这个方法:
如果该变量没有定义,则设为 ON,否则保持用户指定的值不变。
这样当用户没有指定 BUILD_SHARED_LIBS 这个变量时,会默认变成 ON。
也就是说除非用户指定了 -DBUILD_SHARED_LIBS:BOOL=OFF 才会生成静态库,否则默认是生成动态库。
if (NOT DEFINED BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS ON)
endif()
常见坑点
动态库无法链接静态库(3.22.1 可以正常链接)
add_library(otherlib STATIC otherlib.cpp)
add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC otherlib)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)
让静态库编译时也生成位置无关的代码(PIC),这样才能装在动态库里
也可以只针对一个库,只对他启用位置无关的代码(PIC)
add_library(otherlib STATIC otherlib.cpp)
set_property(TARGET otherlib PROPERTY POSITION_INDEPENDENT_CODE ON)
add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC otherlib)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)