CMake学习

CMake

编译器是一个根据源代码生成机器码的程序

单文件编译

main.cpp

#include <iostream>
int main()
{
    std::cout << "hello world" << std::endl;
    return 0;
}

g++ main.cpp -o a.out 这个命令会调用g++编译器,让他读取main.cpp中的源码,并根据C++标准生成相应的机器指令码,输出到a.out中,a.out就是可执行文件

./a.out 这个命令是让操作系统读取刚生成的可执行文件,从而执行其中编译成的机器码,调用对应的函数并且输出hello world

多文件编译和链接

像上面的单个文件编译虽然方便,但是有如下缺点:

  • 所有的代码都堆在一起,不利于模块化和理解
  • 工程代码变大时,编译时间变的过长,改动一个地方就得全部重新编译。

因此,我们提出了多文件编译的概念,文件之间通过符号声明相互引用

g++ -c hello.cpp -o hello.o

g++ -c main.cpp -o main.o

其中使用-c选项是指定生成临时的对象文件main.o,之后在根据一系列的对象文件得到最终的a.out

g++ hello.o main.o -o a.out

为什么需要构建系统(Makefile)

a.out: hello.o main.o
	g++ hello.o main.o -o a.out
hello.o: hello.cpp
	g++ -c hello.cpp -o hello.o
main.o: main.cpp
	g++ -c main.cpp -o main.o

文件越来越多时,一个一个调用g++编译连接会变得很麻烦。于是发明了make这个程序,只要写出不同文件之间的依赖关系,和生成各个文件的规则

make a.out

上述命令可以直接构建出a.out这个可执行文件。和直接使用g++相比,make指明了依赖关系的好处是:

  • 当更新了hello.cpp的时候,只会重新编译hello.o而不需要把main.o也重新编译一遍、
  • 能够自动并行的发起对hello.cpp和main.cpp进行编译,加快编译速度
  • 用通配符批量生成文件规则,避免对每个cpp和o文件重复写g++命令(%.o : %.cpp)

但是坏处也很明显:

  • make需要再unix类系统上是通用的,但是在windows系统上不然
  • 需要准确的指明每个项目之间的依赖关系,有头文件时候会特别头疼
  • make语法简单,不像shell或者python一样可以做很多判断
  • 不同的编译器有不同的flag规则,为g++准备的参数可能对msvc不适用

cmake构建系统

为了解决make的问题,跨平台的cmake应运而生

  • 只需要一份CMakeLists.txt,他就能够在调用时生成当前系统所支持的构建系统
  • cmake可以自动检测源文件和头文件年之间的依赖关系,导出到makefile里
  • cmake具有相对高级的语法,内置的函数能够处理configure,install等常见需求
  • cmake可以自动检测当前的编译器,需要添加那些flag

cmake的命令和调用

cmake_minimum_required(VERSION 3.8)
project(hellocmake LANGUAGES CXX)
add_executable(a.out main.cpp hello.cpp)

读取当前目录的CMakeLists.txt文件,并且在build文件夹下生成Makefile

cmake -B build

让make读取makefile并且开始构建a.out

cmake -C build

以上命令和上一个等价,但是更跨平台

cmake --build build

执行生成的a.out

./a.out

为什么需要库

  • 有时候我们需要多个可执行文件,他们之间用到的某些功能是相同的,我们想把这些共有的功能做成一个库,方便大家使用
  • 库中的函数可以被可执行文件调用,也可以被其它库调用
  • 库文件又分为静态库文件和动态库文件
  • 其中静态库相当于直接把代码插入到生成的可执行文件中,会导致体积变大,但是只需要一个文件即可执行
  • 而动态库则只在生成的可执行文件中生成插桩函数,当可执行文件被加载时会读取指定的目录中的dll文件,加载到内存中空闲的位置,并且替换相应的插桩指向的地址为加载后的地址,这个过程称为重定向。这样以后函数被调用就会跳转到动态加载的地址去。

cmake中的静态库和动态库

  • cmake除了add_executable生成可执行文件之外,还可以通过add_library生成库文件

  • add_library的语法与add_executable大致相同,除了他需要指定的动态库还是静态库

add_library(test STATIC source.cpp)

add_library(test SHARED source.cpp)

  • 动态库有很多坑,特别是windows下,初学者自己创建库时候建议使用静态库
  • 但是他人提供的库大多是动态库,我们之后还要讨论如何使用他人的库
  • 创建库以后要在某个可执行文件中使用该库只需要:

target_link_library(a.out PUBLIC test)

cmake中的子模块

  • 复杂的工程中,我们需要划分子模块,通常一个库一个目录
  • 我们把hellolib的库的文件移动到hellolib文件夹下,里面的CMakeLists.txt定义了hellolib的生成规则
  • 要在根目录使用,可以在cmake的add_subdirectory添加子目录,子目录也包含一个CMakeLists,其中定义的库在add_subdirectory中就可以在外面使用
  • 子目录的CMakeLists.txt里面的路径都是相对路径,这也是很方便的一点

如果想使用子目录中的头文件,可以使用target_include_directories(a.out PUBLIC hellolib)这样就可以直接使用头文件名称来索引子目录的头文件了,而不需要输入较多的路径去索引子目录中的头文件。并且我们可以使用<>来索引子目录的头文件。

如果我们不想再外部工程中写该代码,则可以在库中写target_include_directories(a.out PUBLIC .)

其中PUBLIC的作用是表示一个属性在被link的时候要不要向外传播,如果需要则使用PUBLIC,如果不需要则使用PRIVATE

目标的其他命令

除了头文件搜索目录以外,还有一些选项,PUBLIC和PRIVATE对他们是同理的:

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) # 添加一个宏定义
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++标准库的功能,难免会使用一些第三方库。最友好的一类库莫过于纯头文件库了


  • nothing/std
  • magic_enum
  • glm
  • rapidjson
  • range-v3
  • fmt
  • spdlog

上面的库只需要将头文件下载下来然后include进项目即可。缺点是函数直接在头文件里面,没有提前编译,从而需要重复编译同样的内容,编译时间长

引用系统中预安装的第三方库

可以通过find_package命令寻找系统中的第三方库

find_package(fmt REQUIRED)
target_link_libraries(myapp PUBLIC fmt::fmt)

这里为什么是fmt::fmt,而不是fmt

现代CMake认为一个包可以提供多个库,又称为组件。比如TBB这个包,就包含tbb、tbbmalloc、tbbmalloc_proxy这三个组件

因此为了避免冲突,每个包都向右一个独立的命名空间,以::分割

你可以指定使用哪几个组件:

find_package(TBB REQUIRED COMPONENTS tbb tbballoc REQUIRED)
tark_link_libraries(myapp PUBLIC TBB::tbb TBB::tbbmalloc)

更多详细内容请看下一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

turbolove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值