cmake
-
#include<stdio.h>
和#include<cstdio>
的区别//1 #include <stdio.h> int main() { printf("Hello, world!\n"); return 0; } //2 #include <cstdio> int main() { //还提供了前缀的写法,加不加std::都行 std::printf("Hello, world!\n"); return 0; }
-
反汇编命令objdump,如:
objdump -D a.out | less
-
cmake和make对比
make:
- make 在 Unix 类系统上是通用的,但在 Windows 则不然。
- 需要准确地指明每个项目之间的依赖关系,有头文件时特别头疼。
- make 的语法非常简单,不像 shell 或 python 可以做很多判断等。
- 不同的编译器有不同的 flag 规则,为 g++ 准备的参数可能对 MSVC 不适用。
cmake:
-
只需要写一份 CMakeLists.txt,他就能够在调用时生成当前系统所支持的构建系统。
-
CMake 可以自动检测源文件和头文件之间的依赖关系,导出到 Makefile 里。
-
CMake 具有相对高级的语法,内置的函数能够处理 configure,install 等常见需求。
-
CMake 可以自动检测当前的编译器,需要添加哪些 flag。比如 OpenMP,只需要在 CMakeLists.txt 中指明 target_link_libraries(a.out OpenMP::OpenMP_CXX) 即可。
-
cmake的命令行调用
# 读取当前目录的 CMakeLists.txt,并在 build 文件夹下生成 build/Makefile: cmake -B build # 让 make 读取 build/Makefile,并开始构建 a.out: make -C build # 以下命令和上一个等价,但更跨平台: cmake --build build # 执行生成的 a.out: build/a.out
-
cmake中的静态库和动态库
add_library(test STATIC source1.cpp source2.cpp) # 生成静态库 libtest.a add_library(test SHARED source1.cpp source2.cpp) # 生成动态库 libtest.so #创建库以后,要在某个可执行文件中使用该库,只需要: target_link_libraries(myexec PUBLIC test) # 为myexec链接刚刚制作的库 libtest.a
-
cmake中的子模块
复杂的工程中,我们需要划分子模块,通常一个库一个目录,比如:
hellolib -CMakeLists.txt -hello.cpp -hello.h CmakeLists.txt main.cpp //这里我们把 hellolib 库的东西移到 hellolib文件夹下了,里面的 CMakeLists.txt 定义了 hellolib 的生成规则。
要在项目根目录使用他,可以用 CMake 的 add_subdirectory 添加子目录,子目录也包含一个CMakeLists.txt,其中定义的库在 add_subdirectory 之后就可以在外面使用。
# CmakeLists.txt cmake_minimum_required(VERSION 3.12) project(hellocmake LANGUAGES CXX) add_subdirectory(hellolib) add_executable(a.out main.cpp) target_link_libraries(a.out PUBLIC hellolib)
# hellolib/CmakeLists.txt # 子目录的 CMakeLists.txt 里路径名(比如 hello.cpp)都是相对路径,这也是很方便的一点。 add_library(hellolib STATIC hello.cpp) target_include_directories(hellolib PUBLIC .) # 这样可以将该目录加入到头文件搜索路径中 # 如果不希望让引用 hellolib 的可执行文件自动添加这个路径,把 PUBLIC 改成 PRIVATE 即可。这就是他们的用途:决定一个属性要不要在被 link 的时候传播。
-
目标的一些其他选项
对于目标:
target_include_directories(myapp PUBLIC /usr/include/eigen3) # 添加头文件搜索目录 target_link_libraries(myapp PUBLIC hellolib) # 添加要链接的库 target_add_definitions(myapp PUBLIC MY_MACRO=1) # 添加一个宏定义 target_add_definitions(myapp PUBLIC -DMY_MACRO=1) # 与 MY_MACRO=1 等价 target_compile_options(myapp PUBLIC -fopenmp) # 添加编译器命令行选项 target_sources(myapp PUBLIC hello.cpp other.cpp) # 添加要编译的源文件
对于全局(把选项加到所有接下来的目标去):
include_directories(/opt/cuda/include) # 添加头文件搜索目录 link_directories(/opt/cuda) # 添加库文件的搜索路径 add_definitions(MY_MACRO=1) # 添加一个宏定义 add_compile_options(-fopenmp) # 添加编译器命令行选项
-
第三方库,作为纯头文件引入
有时候我们不满足于 C++ 标准库的功能,难免会用到一些第三方库。
最友好的一类库莫过于纯头文件库了,这里是一些好用的 header-only 库:- nothings/stb - 大名鼎鼎的 stb_image 系列,涵盖图像,声音,字体等,只需单头文件!
- Neargye/magic_enum - 枚举类型的反射,如枚举转字符串等(实现方式很巧妙)
- g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库(附带一些常用函数,随机数生成等)
- Tencent/rapidjson - 单纯的 JSON 库,甚至没依赖 STL(可定制性高,工程美学经典)
- ericniebler/range-v3 - C++20 ranges 库就是受到他启发(完全是头文件组成)
- fmtlib/fmt - 格式化库,提供 std::format 的替代品(需要 -DFMT_HEADER_ONLY)
- gabime/spdlog - 能适配控制台,安卓等多后端的日志库(和 fmt 冲突!)
只需要把他们的 include 目录或头文件下载下来,然后 include_directories(spdlog/include) 即可。
缺点:函数直接实现在头文件里,没有提前编译,从而需要重复编译同样内容,编译时间长。如使用glm库:
cmake_minimum_required(VERSION 3.12) project(hellocmake LANGUAGES CXX) add_executable(a.out main.cpp) target_include_directories(a.out PUBLIC glm/include)
//main.cpp #include <glm/vec3.hpp> #include <iostream> inline std::ostream &operator<<(std::ostream &os, glm::vec3 const &v) { return os << v.x << ' ' << v.y << ' ' << v.z; } int main() { glm::vec3 v(1, 2, 3); v += 1; std::cout << v << std::endl; return 0; }
-
第三方库,作为子模块引入
第二友好的方式则是作为 CMake 子模块引入,也就是通过 add_subdirectory。
方法就是把那个项目(以fmt为例)的源码放到你工程的根目录。
这些库能够很好地支持作为子模块引入:- fmtlib/fmt - 格式化库,提供 std::format 的替代品
- gabime/spdlog - 能适配控制台,安卓等多后端的日志库
- ericniebler/range-v3 - C++20 ranges 库就是受到他启发
- g-truc/glm - 模仿 GLSL 语法的数学矢量/矩阵库
- abseil/abseil-cpp - 旨在补充标准库没有的常用功能
- bombela/backward-cpp - 实现了 C++ 的堆栈回溯便于调试
- google/googletest - 谷歌单元测试框架
- google/benchmark - 谷歌性能评估框架
- glfw/glfw - OpenGL 窗口和上下文管理
- libigl/libigl - 各种图形学算法大合集
如使用fmt库:
cmake_minimum_required(VERSION 3.12) project(hellocmake LANGUAGES CXX) add_subdirectory(fmt) add_executable(a.out main.cpp) target_link_libraries(a.out PUBLIC fmt)
//main.cpp #include <fmt/core.h> #include <iostream> int main() { std::string msg = fmt::format("The answer is {}.\n", 42); std::cout << msg << std::endl; return 0; }
-
引用系统中预安装的第三方库
可以通过 find_package 命令寻找系统中的包/库:
find_package(fmt REQUIRED) target_link_libraries(myexec PUBLIC fmt::fmt)
为什么是 fmt::fmt 而不是简单的 fmt?
现代 CMake 认为一个包 (package) 可以提供多个库,又称组件 (components),比如 TBB 这个包,就包含了 tbb, tbbmalloc, tbbmalloc_proxy 这三个组件。
因此为避免冲突,每个包都享有一个独立的名字空间,以 :: 的分割(和 C++ 还挺像的)。
你可以指定要用哪几个组件:
find_package(TBB REQUIRED COMPONENTS tbb tbbmalloc REQUIRED) target_link_libraries(myexec PUBLIC TBB::tbb TBB::tbbmalloc)
常用package列表:
- fmt::fmt
- spdlog::spdlog
- range-v3::range-v3
- TBB::tbb
- OpenVDB::openvdb
- Boost::iostreams
- Eigen3::Eigen
- OpenMP::OpenMP_CXX
不同的包之间常常有着依赖关系,而包管理器的作者为 find_package 编写的脚本(例如/usr/lib/cmake/TBB/TBBConfig.cmake)能够自动查找所有依赖,并利用刚刚提到的 PUBLIC PRIVATE 正确处理依赖项,比如如果你引用了 OpenVDB::openvdb 那么 TBB::tbb 也会被自动引用。
其他包的引用格式和文档参考:https://cmake.org/cmake/help/latest/module/FindBLAS.html -
可以使用包管理器来安装第三方库,如apt,pacman,vcpkg。
Pacman 是 Arch Linux 发行版中的包管理器。它负责在 Arch Linux 系统上下载、安装、升级和删除软件包,以及管理系统的依赖关系。Pacman 是 Arch Linux 的核心组件之一,它使得在 Arch Linux 上管理软件包变得非常方便。 Pacman 允许用户从 Arch Linux 的软件仓库中查找和安装软件包,还可以升级系统中已安装的软件包以保持系统最新。此外,Pacman 还能够处理软件包的依赖关系,确保系统上的所有依赖库都得到满足。 Pacman 的命令行界面非常强大,它允许用户执行各种任务,例如: pacman -S package_name:安装一个软件包。 pacman -Syu:升级系统上的所有软件包。 pacman -Q:列出已安装的软件包。 pacman -R package_name:删除一个软件包。 pacman -Ss search_term:在仓库中搜索软件包。 通过 Pacman,Arch Linux 用户可以轻松地管理系统上的软件包,确保系统安全并保持软件包的最新状态。