目录
- 1.
CMAKE的基本使用- 1.1 注释
- 1.2 使用
set命令在cmake中进行变量的定义 - 1.3 CMAKE 中设置 C++ 标准
- 1.4 可执行文件的输出路径
- 1.5 文件的查找
- 1.6 头文件的引入
- 2. 制作动态库和静态库
- 2.1 指定库文件的输出路径
- 3. 包含库文件
- 3.1 链接系统动态库
- 4. 总结与经典面试题
- 4.1 小结
- 4.2 经典面试题
✅️1.CMAKE的基本使用
1.1 注释
CMAKE中使用符号#来表示单行注释,使用#[[ 需要注释的内容 ]]来表示多行注释。
#1.收集文件列表-注释行file(GLOB SRC_LISTS"*.cpp")# 添加构建目标add_executable(main ${SRC_LISTS})#3.添加依赖库列表target_link_libraries(main PRIVATE MyMath)#4.设置输出路径set_target_properties(main PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)# 多行注释-注释块 #[[打印库文件相对main的相对路径 说明:上面打印的就是"获取静态库的输出路径"相对 main 所在目录的结果]]1.2 使用set命令在cmake中进行变量的定义
❓ 问题
为了解决CMakeLists.txt文件中某些变量被反复使用,比如某些.cpp或.h源文件需要在不同的地方被反复使用,如果每次都在需要的位置编写对应的源文件会很麻烦,此时就可以使用set命令使用一个变量来代替这些源文件,当在其他地方使用这些源文件时,就只需要使用这个> 变量来表示即可。
# 语法如下 # 注意:变量名一般都是大写set(VAR[VAL])#[]中的参数是可选项,不需要是可以不用写 # 其中 VAL 可以是字符串、列表、变量、表达式、字符串列表、字符串变量、字符串表达式、列表变量、列表表达式 # 如果不写 VAL,则 VAR 的值为空 # 举例如下set(SRC main.cpp add.cpp sub.cpp)set(SRC main.cpp;add.cpp;sub.cpp)# 源文件之间可以使用空格隔开,亦可以使用;来隔开add_executable(test ${SRC})# 当使用变量时,需要使用 ${变量名}的形式1.3 CMAKE中设置C++标准
🐳 问题:当我们需要在
cmake中指定使用的C++标准时,使用使用cmake中一个宏:DCMAKE_CXX_STANDARD来进行设置。
# 使用DCMAKE_CXX_STANDARD指定C++标准版本 # 方式1:在构建文件中指定c++标准set(CMAKE_CXX_STANDARD17)set(CMAKE_CXX_STANDARD_REQUIRED ON)# 强制要求使用C++17标准 # 方式2在执行cmake命令的时候指定c++标准#cmakeCMakeLists.txt文件路径-DCMAKE_CXX_STANDARD=171.4 可执行文件的输出路径
cmake中可以使用一个宏EXECUTABLE_OUTPUT_PATH和set函数配合使用来设置可执行文件的输出路径。
举例如下:
# 定义一个绝对路径set(APP/home/lj/cmake)# 如果/home/lj/cmake路径不存在,cmake会自动生成,无需手动创建set(EXECUTABLE_OUTPUT_PATH ${APP}/bin)1.5 文件的查找
在cmake中,查找文件的函数有两个:file命令和aux_source_directory命令.
#aux_source_directory# 使用如下命令查找某个路径下的所有源文件 # 参数1:表示要搜索的路径 # 参数2:表示查找到的文件列表存储在哪个变量中aux_source_directory(dir variable)# 举例如下:aux_source_directory(src SRC_LIST)add_executable(TestMyMath ${SRC_LIST})#file# 语法如下:#GLOB表示在指定路径下查找所有符合条件的文件,并将满足条件的文件列表保存到变量中#GLOB_RECURSE表示在指定路径下递归查找所有符合条件的文件,并将满足条件的文件列表保存到变量中,包括子目录中的文件#file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/my_lib/src/*.cpp) file(GLOB MAIN ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) # 注意:此处的CMAKE_CURRENT_SOURCE_DIR宏表示当前CMakeLists.txt文件所在的目录,即项目根目录1.6 头文件的引入
🐳在编译大型项目的时候,我们往往需要将头文件的路径指定出来,使其在编译过程中方便使用这些头文件来完成编译工作,可以通过
include_directories命令来解决。
命令原型如下:
include_directories(项目中头文件所在的路径)示例:
[root@localhost my_math]# tree my_lib/my_lib/ ├── CMakeLists.txt ├── include │ └── math.h └── src ├── add.cpp └── sub.cpp2directories,4files对应的CMakeLists.txt文件如下:
cmake_minimum_required(VERSION3.20)project(MyMath LANGUAGES CXX)set(CMAKE_CXX_STANDARD17)set(BIN/home/lj/cmake/build/)set(EXECUTABLE_OUTPUT_PATH ${BIN}/bin)include_directories(${PROJECT_SOURCE_DIR}/my_lib/include)# 引入头文件file(GLOB SRC_LISTS ${PROJECT_SOURCE_DIR}/my_lib/src/*.cpp) add_executable(math ${SRC_LISTS})✅️ 2.制作动态库和静态库
在
cmake中,如果我们需要制作一个静态库,需要使用如下命令:
# 参数1:表示需要生成的库的名称 # 参数2:STATIC表示生成静态库 # 后续参数:表示生成库所需要的所有源文件add_library(库名称 STATIC 源文件1,【源文件列表...】)此处需要注意,Linux和Windows中对于库的后缀是不同的,对于静态库,Linux中使用后缀.a,而对于WIndows中使用后缀.lib;对于动态库,Linux使用后缀.so,Windows中使用后缀.dll。但是在使用cmake中的add_library命令不需要指定后缀,cmake会根据不同的平台,添加对应的后缀名。
完整示例如下:
[root@localhost static_lib]# tree ../static_lib/../static_lib/ ├── build ├── CMakeLists.txt ├── include │ └── head.h ├── main.cpp └── src ├── add.cpp └── sub.cpp3directories,5files完整CMakeLIsts.txt文件如下
cmake_minimum_required(VERSION3.18)project(MATH)include_directories(${PROJECT_SOURCE_DIR}/include)# 指定头文件所在目录file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) add_library(math STATIC ${SRC_LIST}) # 生成静态库完整构建过程如下:
[root@localhost build]# ll总用量0[root@localhost build]# cmake ../-- The C compiler identification is GNU9.2.1 -- The CXX compiler identification is GNU9.2.1 -- Detecting C compiler ABI info -- Detecting C compiler ABI info -done-- Checkforworking C compiler: /opt/rh/gcc-toolset-9/root/usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features -done-- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info -done-- Checkforworking CXX compiler: /opt/rh/gcc-toolset-9/root/usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features -done-- Configuringdone(0.8s)-- Generatingdone(0.0s)-- Build files have been written to: /home/lijing/cmake/static_lib/build[root@localhost build]# make[33%]Building CXX object CMakeFiles/math.dir/src/add.cpp.o[66%]Building CXX object CMakeFiles/math.dir/src/sub.cpp.o[100%]Linking CXX static library libmath.a[100%]Built target math[root@localhost build]# tree ../../static_lib/../../static_lib/ ├── build │ ├── CMakeCache.txt │ ├── CMakeFiles │ │ ├──3.28.3 │ │ │ ├── CMakeCCompiler.cmake │ │ │ ├── CMakeCXXCompiler.cmake │ │ │ ├── CMakeDetermineCompilerABI_C.bin │ │ │ ├── CMakeDetermineCompilerABI_CXX.bin │ │ │ ├── CMakeSystem.cmake │ │ │ ├── CompilerIdC │ │ │ │ ├── a.out │ │ │ │ ├── CMakeCCompilerId.c │ │ │ │ └── tmp │ │ │ └── CompilerIdCXX │ │ │ ├── a.out │ │ │ ├── CMakeCXXCompilerId.cpp │ │ │ └── tmp │ │ ├── cmake.check_cache │ │ ├── CMakeConfigureLog.yaml │ │ ├── CMakeDirectoryInformation.cmake │ │ ├── CMakeScratch │ │ ├── Makefile2 │ │ ├── Makefile.cmake │ │ ├── math.dir │ │ │ ├── build.make │ │ │ ├── cmake_clean.cmake │ │ │ ├── cmake_clean_target.cmake │ │ │ ├── compiler_depend.make │ │ │ ├── compiler_depend.ts │ │ │ ├── DependInfo.cmake │ │ │ ├── depend.make │ │ │ ├── flags.make │ │ │ ├── link.txt │ │ │ ├── progress.make │ │ │ └── src │ │ │ ├── add.cpp.o │ │ │ ├── add.cpp.o.d │ │ │ ├── sub.cpp.o │ │ │ └── sub.cpp.o.d │ │ ├── pkgRedirects │ │ ├── progress.marks │ │ └── TargetDirectories.txt │ ├── cmake_install.cmake │ ├── libmath.a# Linux环境下生成的静态库 - 其组成是 lib + 静态库名称math + .a│ └── Makefile ├── CMakeLists.txt ├── include │ └── head.h ├── main.cpp └── src ├── add.cpp └── sub.cpp13directories,39files制作动态库,同样使用命令add_library,只需要将第二个参数STATIC改为SHARED即可,最终会生成libmath.so文件。
cmake_minimum_required(VERSION3.18)project(MATH)include_directories(${PROJECT_SOURCE_DIR}/include)# 指定头文件所在目录file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) add_library(math SHARED ${SRC_LIST}) # 生成动态库完整构建过程:
[root@localhost cmake]# cd shared_lib/[root@localhost shared_lib]# cd build/[root@localhost build]# cmake ../-- The C compiler identification is GNU9.2.1 -- The CXX compiler identification is GNU9.2.1 -- Detecting C compiler ABI info -- Detecting C compiler ABI info -done-- Checkforworking C compiler: /opt/rh/gcc-toolset-9/root/usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features -done-- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info -done-- Checkforworking CXX compiler: /opt/rh/gcc-toolset-9/root/usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features -done-- Configuringdone(0.6s)-- Generatingdone(0.0s)-- Build files have been written to: /home/lijing/cmake/shared_lib/build[root@localhost build]# make[33%]Building CXX object CMakeFiles/math.dir/src/add.cpp.o[66%]Building CXX object CMakeFiles/math.dir/src/sub.cpp.o[100%]Linking CXX shared library libmath.so[100%]Built target math[root@localhost build]# tree ../../shared_lib/../../shared_lib/ ├── build │ ├── CMakeCache.txt │ ├── CMakeFiles │ │ ├──3.28.3 │ │ │ ├── CMakeCCompiler.cmake │ │ │ ├── CMakeCXXCompiler.cmake │ │ │ ├── CMakeDetermineCompilerABI_C.bin │ │ │ ├── CMakeDetermineCompilerABI_CXX.bin │ │ │ ├── CMakeSystem.cmake │ │ │ ├── CompilerIdC │ │ │ │ ├── a.out │ │ │ │ ├── CMakeCCompilerId.c │ │ │ │ └── tmp │ │ │ └── CompilerIdCXX │ │ │ ├── a.out │ │ │ ├── CMakeCXXCompilerId.cpp │ │ │ └── tmp │ │ ├── cmake.check_cache │ │ ├── CMakeConfigureLog.yaml │ │ ├── CMakeDirectoryInformation.cmake │ │ ├── CMakeScratch │ │ ├── Makefile2 │ │ ├── Makefile.cmake │ │ ├── math.dir │ │ │ ├── build.make │ │ │ ├── cmake_clean.cmake │ │ │ ├── compiler_depend.make │ │ │ ├── compiler_depend.ts │ │ │ ├── DependInfo.cmake │ │ │ ├── depend.make │ │ │ ├── flags.make │ │ │ ├── link.txt │ │ │ ├── progress.make │ │ │ └── src │ │ │ ├── add.cpp.o │ │ │ ├── add.cpp.o.d │ │ │ ├── sub.cpp.o │ │ │ └── sub.cpp.o.d │ │ ├── pkgRedirects │ │ ├── progress.marks │ │ └── TargetDirectories.txt │ ├── cmake_install.cmake │ ├── libmath.so#此时生成的是Linux环境下的动态库文件 - lib + 动态库名称 + .so│ └── Makefile ├── CMakeLists.txt ├── include │ └── head.h ├── main.cpp └── src ├── add.cpp └── sub.cpp13directories,38files2.1 指定库文件的输出路径
Linux环境下,对于使用cmake制作的动态库默认是可以直接执行的,故可以按照可执行文件的输出路径执行方式来进行库文件的路径指定。
具体CMakeLists.txt文件片段如下:
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)# 设置动态库的生成路径除了上面的方式,还有一种方式可以同时适配动态库和静态库,这也是本人推荐和经常使用的一种方式。可以使用宏LIBRARY_OUTPUT_PATH.
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)# 设置库文件输出路径add_library(math SHARED ${SRC})add_library(math STATIC ${SRC})# 上面的动态库和静态库文件都放到同一个路径下完整构建如下:
[root@localhost build]# cmake ../-- Configuringdone(0.0s)-- Generatingdone(0.0s)-- Build files have been written to: /home/lijing/cmake/static_lib/build[root@localhost build]# make[16%]Building CXX object CMakeFiles/staticMath.dir/src/add.cpp.o[33%]Building CXX object CMakeFiles/staticMath.dir/src/sub.cpp.o[50%]Linking CXX static library /home/lijing/cmake/static_lib/lib/libstaticMath.a[50%]Built target staticMath[66%]Building CXX object CMakeFiles/sharedMath.dir/src/add.cpp.o[83%]Building CXX object CMakeFiles/sharedMath.dir/src/sub.cpp.o[100%]Linking CXX shared library /home/lijing/cmake/static_lib/lib/libsharedMath.so[100%]Built target sharedMath[root@localhost build]# ll总用量28-rw-r--r--.1root root1438811月412:55 CMakeCache.txt drwxr-xr-x.6root root26811月412:56 CMakeFiles -rw-r--r--.1root root163411月412:56 cmake_install.cmake -rw-r--r--.1root root707811月412:56 Makefile[root@localhost build]# cd ..[root@localhost static_lib]# ll总用量8drwxrwxr-x.3lijing lijing8911月412:56 build -rw-rw-r--.1lijing lijing36311月412:56 CMakeLists.txt drwxrwxr-x.2lijing lijing2011月412:22 include drwxr-xr-x.2root root5311月412:56 lib -rw-rw-r--.1lijing lijing14811月412:29 main.cpp drwxrwxr-x.2lijing lijing3611月412:22 src[root@localhost static_lib]# tree lib/lib/ ├── libsharedMath.so └── libstaticMath.a0directories,2files✅️ 3.包含库文件
🐳 在一个大型项目中,我们往往需要使用一些我们自己构建的或者额外的动态库或者静态库。
那么,我们如何在我们自己的项目中引入想要的库呢?
此时我们就需要使用连接库的命令 link_libraries。
语法如下:
link_libraries(静态库 【静态库列表】)# 可以导入多个库文件📌 注意:
1.上述命令的参数:可以是libxxx.a这种全名,也可以是去掉lib和后缀.a的名称xxx.
2.如果需要连接的库不是系统提供的,是第三方库,就需要使用link_directories来指定第三三方库所在的位置,具体语法如:link_directories(第三方库所在位置(路径))。
示例如下:
cmake_minimum_required(VERSION3.18)project(MATH)include_directories(${PROJECT_SOURCE_DIR}/include)# 指定头文件所在目录file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) link_directories(${PROJECT_SOURCE_DIR}/lib) # 包含静态库路径 link_libraries(staticMath) # 连接静态库 add_executable(testStatic ${SRC})上述示例演示了如何在我们的项目中链接静态库,如果需要链接动态库,我们需要使用如下命令
targt_link_libraries(target<PRIVATE|PUBLIC|INTERFACE><库>【...】)该命令用于指定一个目标在编译时需要链接哪些库,可以通过这个命令来指定库的名称,路径以及链接顺序。
📌 说明:
1.参数1:target表示谁需要链接库,可以是一个源文件,动态库、静态库 或者可执行文件。
2.参数2:动态库的访问权限,默认是PUBLIC.如果各个库之间没有依赖关系,使用默认权限即可。
3.动态库的链接具有传递性,即a链接了b和c,x链接了a,x也间接的链接了b和c.
关于目标链接库的权限解释:
-PUBLIC:在public后面的库会被链接到前面的target中,并且里面的符号也会被导出。
-PRIVATE:当前目标需要这个库,同时依赖当前目标的其他目标也需要这个库。
-INTERFACE:当前目标编译时不需要这个库,但依赖当前目标的其他目标在编译时必须链接这个库。通常用于纯头文件库(Header-only libraries)
📦场景比喻:借工具
假设 A 是你正在写的库,B 是用了 A 的上层项目,C 是 A 依赖的底层库。
三种权限对 B 的影响如下表:
| 关键字 | 关系描述 | 对 B(使用者)的影响 |
|---|---|---|
PRIVATE | A 用了 C,但 A 把 C 封装得很好。 | B 不需要知道 C 的存在,B 直接链接 A 就能跑。 |
INTERFACE | A 只是个头文件壳子,核心功能全靠 C。 | A 自己编译不需要 C,但 B 链接 A 时,必须同时链接 C。 |
PUBLIC | A 用了 C,且 A 的头文件里暴露了 C 的类型。 | B 链接 A 时,必须同时链接 C,否则 B 编译会报错(找不到 C 的定义)。 |
3.1 链接系统动态库
📚 动态库和静态库的区别:
1.静态库会在生成可执行程序的链接阶段被打包到可执行程序中,后续可执行程序启动时,静态库就会被加载到内存中。
2.动态库在生成可执行程序的连接阶段不会被打包到可执行程序中,只有当可执行程序启动并且调用动态库时,静态库才会被加载到内存。
# 目标在链接系统动态库的时候,写法如下:target_link_libraries(test pthread)链接第三方动态库,只需要使用命令include_directories(库路径)来指定第三方库的路径即可,链接方式和连击系统库的方式相同,如下:
include_directories(${PROJECT_SOURCE_DIR}/lib)target_link_libraries(test pthread sharedLib)📌说明:
对于上述的两个链接命令,其实都可以用于链接动态库和静态库,只是使用方式和场景不同而已,但是这里推荐使用target_link_libraries命令,因为这个命令更加精确,使用起
✅️ 4.总结与经典面试题
4.1 小结
本文从CMake的基础用法出发,依次梳理了注释、变量定义(set)、C++ 标准设置(CMAKE_CXX_STANDARD)、可执行文件输出路径(EXECUTABLE_OUTPUT_PATH)、文件查找(file/aux_source_directory)以及头文件引入(include_directories)等常用命令;随后讲解了如何用add_library制作静态库(STATIC)与动态库(SHARED),并通过LIBRARY_OUTPUT_PATH统一库文件输出路径;最后介绍了link_libraries与target_link_libraries两种链接方式,重点辨析了PRIVATE/INTERFACE/PUBLIC三种链接权限的传递性差异。
一句话概括:CMake的核心思路是「先描述目标,再描述依赖」——用命令把源文件、头文件、库、输出路径等信息声明清楚,剩下的交给CMake去生成对应平台的构建文件。
4.2 经典面试题
Q1:CMake中静态库和动态库有什么区别?分别用什么命令生成?
- 静态库在链接阶段会被完整打包进可执行程序,程序体积较大,但部署时无需额外携带库文件;动态库在链接阶段只记录引用,运行时才加载,多个程序可共享同一份库,节省内存,但部署时需要保证库文件可被找到。
- 生成命令均为
add_library,第二个参数为STATIC生成静态库,为SHARED生成动态库,例如add_library(math STATIC ${SRC})。 - 后缀差异:
Linux下静态库为.a、动态库为.so;Windows下静态库为.lib、动态库为.dll。使用CMake时无需手动写后缀,它会按平台自动补全。
Q2:target_link_libraries中PRIVATE、INTERFACE、PUBLIC有什么区别?
假设 A 是当前库,B 是依赖 A 的上层项目,C 是 A 依赖的底层库:
PRIVATE:A 内部使用 C,但不对外暴露。B 只需链接 A,无需关心 C。INTERFACE:A 自身编译不需要 C,但使用 A 的目标必须链接 C,典型场景是纯头文件库。PUBLIC:A 既自己使用 C,又把 C 暴露给使用者,B 链接 A 时必须同时链接 C。
记忆口诀:PRIVATE自己用,INTERFACE别人用,PUBLIC都用到。
Q3:link_libraries和target_link_libraries有什么区别?推荐用哪个?
link_libraries是「全局」设置,作用于其后定义的所有目标,作用范围大、不够精确,且不便于表达依赖权限。target_link_libraries针对具体目标设置,可以精确指定链接的库、路径、顺序以及PRIVATE/INTERFACE/PUBLIC权限,支持依赖传递。- 实际项目中推荐使用
target_link_libraries,可读性和可维护性更好。
Q4:如何在CMake中指定 C++ 标准?
有两种常用方式:
- 在
CMakeLists.txt中设置:set(CMAKE_CXX_STANDARD 17),配合set(CMAKE_CXX_STANDARD_REQUIRED ON)强制要求该标准。 - 在执行命令时传入:
cmake <CMakeLists.txt 路径> -DCMAKE_CXX_STANDARD=17。
Q5:file(GLOB ...)和aux_source_directory都能查找源文件,如何选择?
aux_source_directory(dir var)只能收集指定目录下的源文件,不递归子目录,写法简单。file(GLOB var path/*.cpp)支持通配符匹配,GLOB_RECURSE还能递归子目录,灵活性更高。- 需要注意的是,
GLOB在新增文件后不会自动触发重新配置,通常需要重新执行cmake;对文件列表变化频繁的项目,显式列出源文件反而更稳妥。
Q6:EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH有什么区别?
EXECUTABLE_OUTPUT_PATH用于设置可执行文件的输出目录。LIBRARY_OUTPUT_PATH用于设置库文件(动态库和静态库)的输出目录,可同时适配两种库。- 两者都通过
set配合使用,路径不存在时CMake会自动创建,无需手动mkdir。
来更加方便。