Cmakelist写法
c++编译原理
这一节可以不看,主要是辅助理解cmakelist为什么要那么写
-
预处理
在预处理阶段主要是处理一些#号相关的命令,例如
#include #define #ifdef
,在这里实现语句的替换。比如#define m 5
,在这个阶段就会将程序中所有的m替换为5。在
#include "xxx.h" 或者 #include <xxx.h>
中。我们可以把常量、宏定义等放入头文件。但是这要求类型只能被定义和命名一次(定义在头文件中,此后不可在其他文件里再进行重复定义)。这两种语法形式都会让include指令替换为整个文件的内容,但是在未指定路径时预处理器搜索路径的方法不同。不过在我们平时写代码时似乎没有太大的影响,观察了很多代码,大家习惯将自己写的头文件使用带引号形式,在使用标准库时使用尖括号形式。
-
编译处理
-
汇编器
-
链接器
链接分为静态链接和动态链接,动态链接库常是.so文件或.dll文件,而静态链接库是.a或者.lib文件。显然,他们都是前人留下来的轮子,我们只需要直接使用即可。他们的区别如下:
- 静态链接:在编译链接时,会将库文件中的代码全部加入到可执行文件(.o文件)中,不过静态库对函数库的链接放在编译时期完成,程序在运行时和函数库再无关系。
- 动态链接:在程序运行时才会被载入,如果不同的应用程序调用相同的库,那么内存里只需要有一份该共享库的实例,避免了空间浪费、程序更新部署和发布的麻烦。
这些都可以在王道的考研书里找到关于链接的内容,所以我只是简单地写一下。
cmakelist写法
由上图可知,c++编译的过程主要分为预处理、编译、汇编和链接,对应到cmakelist的写法为
-
cmake的一些小操作
-
源代码
如果我们的.cpp文件都放在某一个大目录下(这个目录可以包含其他类型文件如.h文件),则可以这样指定源文件位置:
aux_source_directory(. SRC_LIST)
file(GLOB_RECURSE SRC_LIST ${PROJECT_SOURCE_DIR}/xxx/*.c*)
这里的xxx可以直接替换为该大目录
-
预处理
在工程中我们经常将.h文件放在include文件夹下(或者自己自定义的文件夹),所以将自己的文件夹写进include_directories之中即可。
-
编译
在编译中我们可以指定一些选项,比如
这些选项可以放在cmakeList的最前端
-
生成可执行文件
我们需要将第一步的源代码生成可执行文件,之后再进行链接。因此需要
add_executable(xxx ${SRC_LIST})
这里的xxx是你的可执行文件名字,可以自己指定
-
链接
首先我们需要在指定目录下查找库,因此可以借助find_library
find_library(<var> name [path1 path2 ...])
-
用于存储该命令执行的结果,也就是找到的库的全路径。如果能找到指定库,那么中会存放正常的库路径,否则就是存放NOTFOUND。因此我们也可以借助这一条指令判断是否查找库成功
-
name指定待查找的库名称,可以使用全称如libxxx.a,也可以不带前缀和后缀,直接是xxx
-
path用来指定库的查找路径
-
一些细节
- HINTS: 库的搜索路径一般分为两种:默认搜索路径和附加搜索路径。默认搜索路径包含CMake定义的以CMAKE开头的一些变量(CMAKE_PREFIX_PATH)、标准的系统环境变量(如系统环境变量LIB和PATH定义的路径)、系统默认的库安装路径(/usr或者/usr/lib等)。附加搜索路径即我们在find_library中通过HINTS或PATHS指定的路径。具体的内容可参见Cmake命令之find_library介绍 - 简书 (jianshu.com)
查找结束后即可进行链接
使用
target_link_libraries(可执行文件名称 ${存储库路径的变量})
这里的可执行文件名称同本节“生成可执行文件”中的设置,存储库路径的变量则是在find_library中的var。如果有多个var(有多种库的情况),则执行多条target_link_libraries即可。
-
分层/不分层架构
有时候会发现有的工程会使用多级cmakelist,一般个人写的小demo不必使用。
多级cmakelist的好处在于
- 隐藏实现细节,通过多级cmakelist可以将项目的实现细节隐藏在每个模块内部,只需要在顶层CMakelist里暴露必要的接口和变量即可。
- 有利于分布式开发,多级cmakelist可以让大家各写各的,最后在顶层cmakelist中集成即可。
- 构建顺序控制:如果模块之间存在依赖关系,那么使用多级cmakelist也比较好
总的来说,大型项目使用多级,小型的使用一个就可以了。
假设项目结构如下:
-
顶层cmakelist
一般负责设置项目的一般性信息,如cmake的最低版本要求、项目名称、编译选项等,并且包含对各个子目录中的cmakelist文件进行引用。
-
src/CMakelist
一般不做太多配置,而是将任务委托给各个子模块的cmakelist
-
src/module1/CMakelist
参照上面的cmakelist写法,指定源文件、编译选项、链接库、生成目标代码等
正规组织结构
上两节只是讲了如何使用cmakelist来编译我们的代码,但是在工程中还有一些别的要求。一般来说会把源文件放在src目录中,头文件放入include文件夹下,生成的对象文件放在build文件夹下。
因此我们可以使用
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
这就是指定将生成的elf文件存放到工程根目录下的bin目录。
然后我们再切到build目录下执行cmake .. 和make
,这样cmake运行生成的文件就只会呆在build目录下(这其实是默认在哪执行cmake就将所运行生成的文件放在哪里,好像是可以自己重新定义生成位置,不过我懒得找了)
参考
Linux下的静态链接库和动态链接库有什么区别? - 知乎 (zhihu.com)
(12条消息) 动态链接库和静态链接库的区别_静态链接库和动态链接库的区别_wqfhenanxc的博客-CSDN博客
(12条消息) C++的编译流程_c++编译_Lu Zelin的博客-CSDN博客
(12条消息) C++的编译过程及原理_c++编译原理_qq_43133135的博客-CSDN博客
#include 指令 (C/C++) | Microsoft Learn
(12条消息) Linux下CMake简明教程_linux cmake_爱就是恒久忍耐的博客-CSDN博客
-directive-c-cpp?view=msvc-170)