CMake & GCC 基础
Part I GCC
GNU Compiler GCC/G++ 执行编译工作时分4步:
- 预处理 ,即处理
#define
#include
#if
#ifdef
#pragma
等指令,生成 .i 文件 - 将预处理后的文件转化为汇编程序,生成 .s 文件
- 将汇编程序转化为目标代码(机器语言),生成 .o 文件
- 链接目标代码,生成可执行程序
分别编译至 .i/.s/.o/可执行 文件的命令:
1 | gcc -E xxx |
gcc命令只能编译c和c++程序,c程序以.c为后缀,c++程序以.C或.cpp为后缀
用gcc按上述操作编译c++程序时只能编译至生成.o文件,不能生成可执行文件,因为gcc在链接时默认链接到C的标准库。
链接c++标准库生成可执行文件的方式有两种:
1 | gcc xxx -lstdc++ # 使用gcc命令时加上 -lstdc++ 编译选项,设定链接到c++标准库 |
其他参数
选项 | 解释 |
---|---|
-o | FILE生成指定的输出文件。用在生成可执行文件时。 |
-g | 生成调试信息。GNU 调试器可利用该信息。 |
-w | 不生成任何警告信息。 |
-Wall | 生成所有警告信息。 |
-I(大写i) path | 指定path为第一个寻找头文件的目录,找完了这个目录才去 /usr/include 和 /usr/local/include里找 |
-L path | 指定path为第一个寻找库文件的目录,找完了去 /usr/lib 和 /usr/local/lib里找 |
-l[n] (小写L) | 首先在path里找库名为[n]的动态库文件,其对应的库文件名为lib[n].so |
-x language filename | 指定文件所使用的语言,可使用的参数有 "c", "objective-c", "c-header", "c++", "cpp-output", "assembler", "assembler-wit-cpp",当language置空时,gcc会根据文件名后缀判断类型 |
-DMACRO=DEFN | 以字符串"DEFN"定义 MACRO 宏。 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY。 |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY。 |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY。 |
-m486 | 针对 486 进行代码优化。 |
-O0 | 不进行优化处理。 |
-O 或 -O1 | 优化生成代码。 |
-O2 | 进一步优化。 |
-O3 | 比 -O2 更进一步优化,包括 inline 函数。 |
-shared | 生成共享目标文件。通常用在建立共享库时。 |
-static | 禁止使用共享连接。 |
-UMACRO | 取消对 MACRO 宏的定义。 |
-ansi | 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词 |
-DMACRO | 以字符串"1"定义 MACRO 宏。 |
GCC编译示例:
假设现在我们有三个文件,分别是main.cpp,log.cpp,log.h,其中log.h是log.cpp的声明,而main调用了log.cpp某个函数
方式一:将main之外的cpp分别编译成目标代码,再静态链接
1 | g++ -c log.cpp -o log.o # 编译log.cpp成目标代码log.o |
方式二:将main之外的cpp分别编译成目标代码,再生成静态链接库(.a)并使用
1 | g++ -c log.cpp -o log.o |
方式三:将main之外的cpp分别编译成动态链接库(.so)并使用链接,其中
-fPIC
设置了机器码不依赖与绝对地址,防止被同时多处调用时产生冲突,实现代码安全共享,不加该编译选项的话编译无法通过。另外环境变量
LD_LIBRARY_PATH
如果不进行设置,那么虽然编译的过程不受影响,但是运行可执行文件时仍然无法链接动态库。
1 | # 设置环境变量中默认动态库的路径 (下面-I和-L设置的是编译器的默认搜索路径,二者最好保持一致) |
如果不想每次都设置环境变量,可以直接修改存放于 ~/
路径下的 bash 配置文件 .bashrc
内容:
1 | cd ~ |
或者直接打开 .bashrc
文件,在末尾加上这行代码也行;
Part II CMake
注:个人使用CMake的经验也不是很丰富,部分指令我自己暂时还不能理解,日后接触到的话会另起
概要
CMake : (Cross Platform Make) 一款跨平台的工程构建(编译)工具
特点/优势:① 遵守BSD开源协议 ② 跨平台 ③ 编译构建语句简单、易理解 ④ 高效、可扩展
工作方式: 通过解析并执行组态档 CMakelists.txt 文件的语句,自动生成Makefile文件
与其他编译工具的对比:
- GNU GCC:① 用与C/C++, Java等语言开发 ② 适用于简单的项目,项目复杂时只用gcc难以组织编译架构
- Makefile:① 有条理的gcc编译命令的文件 ② 用make工具执行编译指令 ③ 简单项目可手写Makefile,项目复杂时较困难
- GNU autotools ① 与CMake相似,通过源代码架构生成Makefile的编译工具 ② 效率相对较低 ③ 开发与配置步骤繁琐
- CMake:通过组态档 CMakeLists.txt 文件
CMake语法主题框架
工程配置部分(必需)
设置CMake版本、工程名、编译模式、编译系统语言及语言版本等;
依赖执行部分(必需)
引入外部依赖,生成静态(.a)或动态(.so)链接库,或生成可执行文件等;
其他辅助部分(非必需)
参数打印、目录遍历、条件判断、函数定义等;
常用预定义变量
命令行使用CMake设置预定义变量时,需要加上 -D 前缀表示定义
CMake预定义变量
PROJECT_SOURCE_DIR
:工程根目录;PROJECT_BINARY_DIR
:运行 CMake 命令的目录。建议定义为${PROJECT_SOURCE_DIR}/build
下;CMAKE_INCLUDE_PATH
:环境变量,非 CMake 变量;CMAKE_LIBRARY_PATH
:环境变量;CMAKE_CURRENT_SOURCE_DIR
:当前处理的 CMakeLists.txt 文件所在路径;CMAKE_CURRENT_BINARY_DIR
:target 编译目录;- 使用
add_subdirectory
命令可以更改该变量的值; set(EXECUTABLE_OUTPUT_PATH )
命令不会对该变量有影响,但改变了最终目标文件的存储路径。
- 使用
CMAKE_CURRENT_LIST_FILE
:输出调用该变量的 CMakeLists.txt 的完整路径;CMAKE_CURRENT_LIST_LINE
:输出该变量所在的行;CMAKE_MODULE_PATH
:定义自己的 CMake 模块所在路径;EXECUTABLE_OUTPUT_PATH
:重新定义目标二进制可执行文件的存放位置;LIBRARY_OUTPUT_PATH
:重新定义目标链接库文件的存放位置;PROJECT_NAME
:返回由project
命令定义的项目名称;CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
:用来控制if…else…
语句的书写方式。CMAKE_BUILD_TYPE
:编译类型,可选项Debug/ReleaseCMAKE_INSTALL_PREFIX
:设置可执行文件的存放路径
系统预定义变量
CMAKE_MAJOR_VERSION
:CMake 主版本号,如 3.12.0 中的 3;CMAKE_MINOR_VERSION
:CMake 次版本号,如 3.12.0 中的 12;CMAKE_PATCH_VERSION
:CMake 补丁等级,如 3.12.0 中的 0;CMAKE_SYSTEM
:系统名称,例如 Windows-10.0.17134;CMAKE_SYSTEM_NAME
:不包含版本号的系统名,如 Windows;CMAKE_SYSTEM_VERSION
:系统版本号,如 10.0.17134;CMAKE_SYSTEM_PROCESSOR
:处理器架构,如 AMD64;UNIX
:在所有的类 UNIX 平台为 true,包括 macOS 和 Cygwin;WIN32
:在所有的 Win32 平台为 true,包括 Cygwin
开关选项
BUILD_SHARED_LIBS
:控制默认的库编译方式;如果未进行设置,使用 add_library 时又没有指定库类型,默认编译生成的库都是静态库。
CMAKE_C_FLAGS
:设置 C 编译选项;CMAKE_CXX_FLAGS
:设置 C++ 编译选项。
基本指令
大小写习惯:指令全小写,预定义变量全大写
变量使用
${}
方式取值,但是在if
控制语句中是直接使用变量名。
常用构建语法:
cmake_minimum_required
功能:限制使用的cmake最低版本;
格式:cmake_minimum_required(VERSION X.x.x);
示例:cmake_minimum_required (4.1.2);
project
功能:设定项目名;
格式:project(<PROJECT-NAME>);
示例:project (test)
set
功能:创建变量或指定已存在的变量的值;
格式:set(<variable> <value>);
1 | # 示例 |
add_definitions
功能:为源文件的编译整体添加由 -D
引入的宏定义 #define
;
格式:add_definitions(-D <def>)
;
示例:add_definitions(-D DEBUG);
find_package
功能:查找并引入外部的包;
1 | find_package (<PackageName> [version] [EXACT] [QUIET] [MODULE] |
add_subdirectory
功能:添加一个目录作为子目录用于构建;
格式:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
;
source_dir
:目录路径,使用相对路径时默认当前路径为工程根目录,即
${PROJECT_SOURCE_DIR}
;
binary_dir
:中间二进制与目标二进制文件的存放路径;
EXCLUDE_FROM_ALL
:
默认情况下将该目录从编译过程中排除;
示例:add_subdirectory (./toolkit)
include_directories
功能:添加外部库的搜索路径,用空格分割;
格式:link_directories(directory1 directory2 …);
示例:link_directories(/home/lightshaker /usr/local/lib);
aux_source_directory
功能:查找指定路径下的所有源文件,并将源文件列表存储到指定变量中;
格式:aux_source_directory(<dir> <variable>)
;
示例:aux_source_directory(. SRC_LIST)
(需要提前创建变量SRC_LIST);
add_library
功能:生成动态/静态链接库文件;
1 | add_library (<name> [STATIC|SHARED|MODULE] # 库文件名 库类型(静态|) |
add_custom_command
功能一: 创建一个自定义的构建规则来产生一个输出;
1 | add_custom_command (OUTPUT output1 [output2 ...] # output 输出量的存放载体变量 |
功能二:创建目标并为其添加一个自定义命令,可指定命令的执行时刻,若目标已存在则不执行命令 ;
1 | add_custom_command (TARGET <target> # target 目标名 |
add_custom_target
功能:增加一个指定名称的目标,并执行指定的命令,但该目标没有输出文件;
1 | add_custom_target (Name [ALL] [command1 [args1...]] |
该命令可与add_custom_command配合使用,如:
1 | set (TEST_FILE "log.txt") |
输出结果:
1 | [100%] This is a test |
add_executable
功能:使用给定的源文件,生成一个可执行文件;
示例:add_executable(TEST main.cpp log.cpp)
set_target_properties
功能:设置目标属性以改变构建方式,格式与例子如下:
1 | set_target_properties (target PROPERTIES |
1 | set_target_properties(test PROPERTIES |
target_link_libraries
功能:将目标文件链接外部库;
格式:target_link_libraries(<target> … <item>…)
;
示例:target_link_libraries (project ${OpenCV_LIBS})
target_include_directories
功能:指定在编译指定目标时所使用的include目录;
格式:target_include_directories(<target> [SYSTEM][BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items])
;
说明:interface表示该库的接口只为别人开放,自己不使用,public表示自己与他人一并使用,private与interface相反;
enable_testing/add_test
功能:前者用于控制Makefile是否构建目标,后者配合前者使用,格式如下:
1 | add_test(NAME <name> [CONFIGURATIONS [Debug|Release|...]] |
install
功能:用于定义安装规则,安装的内容可包括目标二进制、动态库、静态库以及文件、目录、脚本等;
示例:将二进制文件myrun、动态库文件mylib、静态库文件mystatic分别安装到build目录下的bin、lin、libstatic文件夹中
1 | # "安装" 其实就是复制粘贴的过程 |
基本逻辑控制语句:
foreach endforeach
功能:循环操作
示例:分别为列表、范围、范围步进语法
1 | // 列表语法 |
message
功能:打印消息
1 | message(${PROJECT_SOURCE_DIR}) |
if elif else endif
功能:条件语句
基本用法:
if(<expression>)
:当expression
不为0
、OFF
、NO
、FALSE
、N
、IGNORE
、NOTFOUND
、空字符串,或者不含后缀-NOTFOUND
时,为真;if(NOT <expression>)
:与上一条相反;if(<expr1> AND <expr2>)
以及if(<expr1> OR <expr2>)
if(COMMAND command-name)
:如果command-name
是命令、宏或函数并可调用,为真;if(EXISTS path-to-file-or-directory)
:如果给定路径的文件或目录存在,为真;if(file1 IS_NEWER_THAN file2)
:当 file1 比 file2 新,或 file1/file2 中有一个不存在时为真,文件名需使用全路径;if(IS_DIRECTORY path-to-directory)
:当给定路径是目录时,为真。注意使用全路径;if(DEFINED <variable> )
:如果变量已被定义,为真;if( <variable|string> MATCHES regex)
:当给定变量或字符串能匹配正则表达式 regex 时,为真。此处的variable
直接使用变量名,而非${variable}
。if(<var> LESS <number>)
orif(<var> GREATER <number>)
orif(<var> EQUAL <number>)
数字比较if<var1> STRLESS <var2>
orif<var1> STRGREATER <var2>
orif<var1> STREQUAL <var2>
字母表顺序比较
1 | // 示例一 |
while endwhile
功能:循环操作
1 | while(<condition>) |
option
功能:控制某个变量或宏的定义与否,选项ON/OFF,如果变量在之前没有定义过,则在执行option时定义该变量
1 | # 编译脚本传入宏定义 TEST_DEBUG |
参考资料:
https://www.bilibili.com/video/BV17J411m7o1
https://www.jianshu.com/p/9763cd8325bf
https://www.jianshu.com/p/035bc18f8f62