Linux|Ubuntu系统下利用cmake工具进行C++的编译
前言
在Linux|Ubuntu系统下利用cmake工具进行C++的编译,我们需要掌握一些基本知识:
- Linux系统的一些基本命令行语句
- cmake工具的使用方法
- C++的编译流程
1.Linux|Ubuntu系统的基本命令行语句
在Ubuntu系统中,我们需要掌握一些常用的命令行语句,因为我们并不搞Linux系统的运维工作,所以只需要知道一些基础的足够我们应用的命令行语句即可,真正需要用到其他的再上网查找即可。
笔者列出了如下一些比较基础的命令行语句:
命令 | 作用 |
---|---|
cd (目录名) | 打开目录 |
cat (文件名) | 在终端查看文本内容(不能修改) |
ls -l (目录名) | 以长格式显示文件的目录和详细信息、权限、所有者、大小、修改日期 |
ls -a (目录名) | 显示所有文件和目录,包括以点开头的隐藏文件 |
ls -h (目录名) | 显示单位大小 |
stat (目录名/文件名) | 显示目录/文件的详细信息 |
tree (目录名) | 树状表的形式显示目录和文件 |
mkdir (目录名) | 创建一个新的目录 |
touch (文件名) | 创建一个新的文件 |
rm (文件名) | 删除文件 |
rm -r (目录名) | 删除目录 |
rm -f (目录名) | 强制删除,不带提示 |
rmdir (目录名) | 删除目录 |
mv (文件/目录名)(目录) | 移动(文件/目录名)到(目录),加 -i ,同名文件询问是否覆盖 |
mv (旧名称)(新名称) | 重命名,加 -i ,同名文件询问是否覆盖 |
cp (旧文件名)(新文件名) | 复制文件, -i ,同名文件询问是否覆盖 |
cp -r (旧目录名)(新目录名) | 复制目录, -i ,同名文件询问是否覆盖 |
这些是笔者认为非常基础的命令,当然还有一些文件查找以及线程的命令就不一一列举了,除此以外,文件权限也是需要了解的一项内容:
权限类型 | 作用 |
---|---|
r | 可读,可进入目录/可读取普通文件内容 |
w | 可写,(前提是有r权限先进入)可在目录中创建或删除目录或文件,可修改目录下的普通文件 |
x | 可执行/切换目录,(前提是有r权限先进入)可访问目录下的文件或子目录或可执行文件 |
- | 无权限 |
例d rwx rwx rwx是一个文件或目录的权限信息:第一项为文件类型,第一组的三个参数为所有者的权限,第二组的三个参数为用户组的权限,第三组的三个参数为其他人的权限。
apt是Ubuntu中自带的一个软件包管理工具,可以自动解决软件的依赖问题,是一个必须要了解的工具。
2.cmake工具
在Ubuntu终端中输入sudo apt install cmake
,下载cmake工具,首先我们需要了解为什么需要cmake工具,cmake工具是干嘛用的。
笔者喜欢使用C++进行程序编写,所以要用到g++编译器,在工程比较小的时候,可以手动使用g++对源码文件进行编译,但工程量一旦增加,需要对大量的源码文件进行编译,而且在进行编译的时候,需要链接很多额外的库,这是一件非常麻烦的事情,因此不知道哪位大佬想到了去执行一个配置文件,在配置文件中提前写好要进行的编译过程,这个配置文件就叫做makefile,去执行这个makefile的工具叫做make,但是随着时间的流逝,大家发现makefile也挺麻烦的,还存在代码跨平台的一些问题,就又有大佬想到了,去写一个比较简单的CMakeLists.txt文件,然后用cmake工具去执行CMakeLists.txt文件,生成makefile文件,再用make工具执行这个利用CMakeLists.txt文件生成的makefile文件,所以对于现在的程序猿来说,去写一个简单的CMakeLists.txt文件,就可以很方便的对代码进行编译。因此我们需要掌握如何写一个CMakeLists.txt文件,这是一件很重要的事情!!!
cmake就是一种语言,他有自己的函数,就像c++一样,每一个函数都有自己的目的,cmake不像c++那样具有大量灵活的函数,可以执行各种不同的功能,cmake的函数目标明确,生成可执行文件或者库。
生成可执行文件:生成可执行文件,必要的两点:
1.链接源码文件
2.链接库文件(很简单的代码,没有库文件则不用链接,但一般我们接触到的代码都要链接库,第三方库也好,自己做的库也好)
3.包含头文件目录
为什么只包含头文件的目录呢?而不是像包含库文件一样,包含具体的头文件。
包含头文件的实际操作是在源代码中使用 #include 预处理指令来引用特定的头文件,当编译器在编译源代码时遇到 #include 指令,它会根据包含的头文件目录查找相应具体的头文件。因此在源代码中使用 #include 指令时,只需要提供头文件的相对路径。
只要知道库文件所在目录,需要链接的库文件,头文件所在目录就可以完成可执行文件的生成,除此以外,我们还需要知道生成的这些文件应该保存到哪里,在做这些工作之前,我们应该先了解一下cmake函数的一些常见的预定义的变量(在cmake刚刚开始执行的时候,cmake自动设置生成的一些变量)
生成库文件:必要的一点:
链接源码文件
1. CMAKE_SOURCE_DIR:
这是一个非常重要的预定义变量,默认值是CMakeLists.txt文件所在目录的绝对路径。
2. CMAKE_BINARY_DIR:
默认值是当前cmake的工作路径,在哪个路径执行的cmake命令,工作路径就是哪个位置,一般是在与CMakeLists.txt文件同目录中创建一个build文件夹,在build中执行cmake命令,当然也可以在其他位置,建其他名称的文件夹,主要是存放cmake以及make时产生的一些文件,有用的,没用的,不知道是啥的一堆,后面要设置参数把有用的找出来。
3. CMAKE_LIBRARY_OUTPUT_DIRECTORY:
这是动态库的输出路径,默认是在CMAKE_BINARY_DIR这里面,我们一般都是要自己去设置这个值的,放在自己“喜欢”的地方(第三方依赖库会有明确的设置放在哪里,cmake内置了一些第三方库的一些默认安装位置,自己不要乱改)。(有一个老版本的参数叫做LIBRARY_OUTPUT_PATH,都一样)
4. CMAKE_ARCHIVE_OUTPUT_DIRECTORY:
这是静态库的输出路径,默认是在CMAKE_BINARY_DIR这里面(同上)。(有一个老版本的参数叫做ARCHIVE_OUTPUT_PATH,都一样)
5. CMAKE_RUNTIME_OUTPUT_DIRECTORY:
这是可执行文件的输出路径,默认是在CMAKE_BINARY_DIR这里面的,自己可以修改位置。(有一个老版本的参数叫做EXECUTABLE_OUTPUT_PATH,都一样)
6.CMAKE_INCLUDE_CURRENT_DIR:
是否将 CMAKE_SOURCE_DIR当作头文件的目录包含到全局(这里提到包含到全局,是因为还有与之对应的,只将头文件的目录包含的目标文件上,而不是作用到全局所有目标文件上)
这里笔者展示一个函数 set()
,是用来改变这些变量的值的。
1.链接源码函数:
add_executable(目标 源码文件)
源码文件可以用相对路径,CMAKE_SOURCE_DIR/…,如果源码文件就在CMAKE_SOURCE_DIR路径(源码文件和CMakeLists.txt文件在一层目录),那么直接写源码文件的文件名即可,如果源码文件在src目录中(src与CMakeLists.txt文件在一层目录),那么必须写成src/源码文件,CMAKE_SOURCE_DIR是可以省略的,因为cmake默认在CMAKE_SOURCE_DIR路径下查找。
2.链接库文件函数:
target_link_directories(目标 库文件所在目录)
连接库文件所在目录到指定目标,链接库文件时从此目录查找,同样的,如果只写了库文件名称,那么cmake也会默认在CMAKE_SOURCE_DIR路径搜索该库文件,当库文件在其他位置时,需要使用绝对路径。
target_link_libraries(目标 库文件)
链接库文件到指定目标,同样的,如果只写了库文件名称,没有进行上一个函数设置库文件所在目录,那么cmake也会默认在CMAKE_SOURCE_DIR路径搜索该库文件,如果库文件正好在CMAKE_SOURCE_DIR路径,则链接成功,否则链接失败。
3.包含头文件目录函数:
target_include_directories(目标 头文件所在目录)
包含头文件的目录到指定目标。
include_directories(头文件所在的目录)
不指定目标,对整个项目中的目标都包含这个头文件目录。
add_library(库名 SHARED 源码文件)
生成动态库
add_library(库名 STATIC 源码文件)
生成静态库
不指定参数,则默认设置为静态库,同样的,如果源码文件在CMAKE_SOURCE_DIR路径,则直接写源码文件名称,否则需要源码文件的绝对路径,如果想连续两次使用此函数,去输出一个静态库和一个动态库,库名不能设置为相同(我也不清楚为啥,这么干就完了,后面我们还可以用其他的函数再改名称)。
set_target_properties()
该函数可以改名称。
笔者根据自己的了解去大概解释一下静态库和动态库
静态库:在编译时,编译器将目标可执行文件需要链接的静态库文件复制一份,打包到可执行程序中,这样会让可执行程序很臃肿,运行速度也会降低
动态库:在编译时,将一个配置文件写入到可执行文件中,在可执行文件要运行的时候,解析编译文件,找到需要链接的动态库的目录,在运行过程中链接动态库
补充两个函数:
findpackage()
,该函数可以用来寻找一些标准第三方库的配置文件,并解析该配置文件,得到这种标准第三方库的库文件,头文件等绝对路径信息,方便使用者更容易地配置第三方库,列如opencv,这种配置文件一般叫做OpenCVConfig.cmake。
还有一个更重的是add_subdirectory()
,添加子目录,一个项目并不是只能有一个CMakeLists.txt文件,可以有很多目录,每个目录中都可以有一个CMakeLists.txt文件,cmake工具就像一个组织者,对于一个庞大的项目,分块组织代码们,最后再聚拢到主CMakeLists.txt文件中,说的很抽象,不想继续解释这里了,笔者的表述能力比较差,如果读者希望了解更多的,笔者熬夜给你造出来!
笔者上文中提到的内容不太完整,但cmake的大致思路就是这样,具体的一些小细节大家自己查查资料,还是很容易查到的。
3.C++的编译调试
直接偷的图
笔者在上文大概只是说了链接相关的部分,因为笔者认为这部分是我们更应该掌握的,预处理阶段也应该了解一下,但对于编译和汇编部分,生成的是啥我都不清楚,我也没用到他们产生的文件,实在不想看了,
我们在写代码的时候不可能写完就是完全正确的,编译链接就能运行,一定会产生错误,因此我们需要进行调试,这就产生了,调试和发布两个版本的可执行文件,调试版本就是我们调试代码,寻找错误的一种方式,是一种半成品,发布版本是我们的代码可以正常运行了,让编译器自动优化一下代码,产生一个发布版本,这是成品。
设置构建类型为 Debug 或 Release
set(CMAKE_BUILD_TYPE "Debug或 "Release")
CMAKE_CXX_FLAGS_DEBUG 是一个 CMake 变量,用于设置在 Debug 构建类型下 C++ 编译器的编译选项。在 CMake 中,CMAKE_CXX_FLAGS_DEBUG 变量用于指定 Debug 构建类型时要传递给 C++ 编译器的选项。通过修改这个变量,您可以在 Debug 构建中设置特定的编译选项。通常,CMAKE_CXX_FLAGS_DEBUG 变量的默认值为空,可以通过在 CMakeLists.txt 文件中设置该变量来添加您需要的选项。例如:
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
优化选项:
-O0、-O1、-O2、-O3:指定不同级别的优化。-O0 表示不进行优化,-O1 表示基本优化,-O2 表示更高级别的优化,-O3 表示最大程度的优化。
-Os:优化代码大小,尽量减小可执行文件的大小。
-Ofast:启用更高级别的优化,可能牺牲一些严格的标准兼容性。
调试选项:
-g:生成调试信息,用于调试可执行文件。
-ggdb:生成更详细的调试信息,用于使用 GDB 进行调试。
警告选项:
-Wall:启用常见的编译警告。
-Werror:将警告视为错误,导致编译过程中出现警告时中止编译。
-Wextra:启用额外的编译警告。
-Wno-:禁用特定的警告。
代码生成选项:
-std=:指定要使用的 C++ 标准版本,如 -std=c++11、-std=c++14、-std=c++17 等。
-fPIC:生成位置无关代码,用于共享库。
-fno-exceptions:禁用 C++ 异常处理。
-fno-rtti:禁用运行时类型信息。