0%

GCC与CMake入门

CMake & GCC 基础

Part I GCC

GNU Compiler GCC/G++ 执行编译工作时分4步:

  1. 预处理 ,即处理 #define #include #if #ifdef #pragma 等指令,生成 .i 文件
  2. 将预处理后的文件转化为汇编程序,生成 .s 文件
  3. 将汇编程序转化为目标代码(机器语言),生成 .o 文件
  4. 链接目标代码,生成可执行程序

分别编译至 .i/.s/.o/可执行 文件的命令:

1
2
3
4
gcc -E xxx
gcc -S xxx
gcc -c xxx
gcc xxx
  • gcc命令只能编译c和c++程序,c程序以.c为后缀,c++程序以.C或.cpp为后缀

  • 用gcc按上述操作编译c++程序时只能编译至生成.o文件,不能生成可执行文件,因为gcc在链接时默认链接到C的标准库。

  • 链接c++标准库生成可执行文件的方式有两种:

1
2
gcc xxx -lstdc++  # 使用gcc命令时加上 -lstdc++ 编译选项,设定链接到c++标准库
g++ xxx # 直接使用g++编译

其他参数

选项 解释
-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
2
g++ -c log.cpp -o log.o  # 编译log.cpp成目标代码log.o
g++ main.cpp log.o -o MAIN # 静态链接

方式二:将main之外的cpp分别编译成目标代码,再生成静态链接库(.a)并使用

1
2
3
g++ -c log.cpp -o log.o
ar rcs liblog.a log.o # 利用目标代码生成静态库
g++ main.cpp -L./ -llog -o MAIN # 使用静态库编译至可执行文件

方式三:将main之外的cpp分别编译成动态链接库(.so)并使用链接,其中 -fPIC 设置了机器码不依赖与绝对地址,防止被同时多处调用时产生冲突,实现代码安全共享,不加该编译选项的话编译无法通过。另外环境变量 LD_LIBRARY_PATH 如果不进行设置,那么虽然编译的过程不受影响,但是运行可执行文件时仍然无法链接动态库。

1
2
3
4
5
6
# 设置环境变量中默认动态库的路径 (下面-I和-L设置的是编译器的默认搜索路径,二者最好保持一致)
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
# 编译log.cpp成动态链接库.so文件,文件名一般是lib[name].so
g++ log.cpp -shared -fPIC -o liblog.so
# -I和-L分别设置默认头文件和动态库的路径(用于lib文件与项目源码不在同一路径下时),-l指定动态库的库名[name]
g++ main.cpp -I ./ -L ./ -l log -o MAIN

如果不想每次都设置环境变量,可以直接修改存放于 ~/ 路径下的 bash 配置文件 .bashrc 内容:

1
2
cd ~
echo "export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH" >> ./.bashrc

或者直接打开 .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/Release
  • CMAKE_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
2
3
4
5
# 示例
set (CMAKE_CXX_STANDARD 14) # 设置编译使用的C++版本为C++14
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") # 设置显示所有警告
set (CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) # 设置生成可执行文件的路径为工程根目录
set (source_files "${PROJECT_SOURCE_DIR}/*.cpp") # 设置源文件包
add_definitions

功能:为源文件的编译整体添加由 -D 引入的宏定义 #define

格式add_definitions(-D <def>)

示例:add_definitions(-D DEBUG);

find_package

功能:查找并引入外部的包;

1
2
3
4
5
6
find_package (<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE]) # 格式

find_package (OpenCV 4.1.2s)
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
2
3
add_library (<name> [STATIC|SHARED|MODULE]  # 库文件名 库类型(静态|)
[EXCLUDE_FROM_ALL]
[source1] [source2 …])
add_custom_command

功能一: 创建一个自定义的构建规则来产生一个输出;

1
2
3
4
5
6
7
8
9
add_custom_command (OUTPUT output1 [output2 ...]              # output 输出量的存放载体变量
COMMAND command1 [ARGS] [args1...] # COMMAND 关键字 command 命令语句
[COMMAND command2 [ARGS] [args2...] ...] # ARGS 命令可能带有的参数 如cp [f1] [f2]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[IMPLICIT_DEPENDS <lang1> depend1
[<lang2> depend2] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM] [APPEND])

功能二:创建目标并为其添加一个自定义命令,可指定命令的执行时刻,若目标已存在则不执行命令 ;

1
2
3
4
5
6
7
8
9
add_custom_command (TARGET <target>                       # target 目标名
PRE_BUILD | PRE_LINK | POST_BUILD # 构建目标前执行|链接依赖项后执行|构建目标后执行
COMMAND command1 [ARGS] [args1...] # 命令
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS])
add_custom_target

功能:增加一个指定名称的目标,并执行指定的命令,但该目标没有输出文件;

1
2
3
4
5
6
add_custom_target (Name [ALL] [command1 [args1...]]
[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM]
[SOURCES src1 [src2...]])

​ 该命令可与add_custom_command配合使用,如:

1
2
3
4
5
6
7
8
9
10
11
12
set (TEST_FILE "log.txt")
add_custom_command (OUTPUT ${TEST_FILE}
COMMAND echo "Generating log.txt file..."
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_FILE} ${TEST_FILE}
COMMENT  "This is a test")

add_custom_target (Test1 ALL DEPENDS ${TEST_FILE})

add_custom_command (TARGET Test1
PRE_BUILD 
COMMAND echo "executing a fake command"
COMMENT "This command will be executed before building target Test1")

​ 输出结果:

1
2
3
4
5
[100%] This is a test
Generating log.txt file...
This command will be executed before building target Test1
executing a fake command
[100%] Built target Test1
add_executable

功能:使用给定的源文件,生成一个可执行文件;

示例:add_executable(TEST main.cpp log.cpp)

set_target_properties

功能:设置目标属性以改变构建方式,格式与例子如下:

1
2
3
set_target_properties (target PROPERTIES 
prop1 value1
prop2 value2 ...)
1
2
3
set_target_properties(test PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")

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
2
3
add_test(NAME <name> [CONFIGURATIONS [Debug|Release|...]]
[WORKING_DIRECTORY dir]
COMMAND <command> [arg1 [arg2 ...]])
install

功能:用于定义安装规则,安装的内容可包括目标二进制、动态库、静态库以及文件、目录、脚本等;

示例:将二进制文件myrun、动态库文件mylib、静态库文件mystatic分别安装到build目录下的bin、lin、libstatic文件夹中

1
2
3
4
5
6
# "安装" 其实就是复制粘贴的过程
install (
TARGETS myrun mylib mystatic
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic)

基本逻辑控制语句:

foreach endforeach

功能:循环操作

示例:分别为列表、范围、范围步进语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 列表语法
aux_source_directory(. SRC_LIST)
foreach(F ${SRC_LIST})
message(${F})
endforeach(F)

// 范围语法
foreach(v RANGE 10)
message(${v})
endforeach(v)

// 范围步进语法
foreach(a RANGE 5 15 3)
message(${a})
endforeach(a)
message

功能:打印消息

1
message(${PROJECT_SOURCE_DIR})
if elif else endif

功能:条件语句

基本用法:

  • if(<expression>):当expression 不为 0OFFNOFALSENIGNORENOTFOUND、空字符串,或者不含后缀 -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>) or if(<var> GREATER <number>) or if(<var> EQUAL <number>) 数字比较

  • if<var1> STRLESS <var2> or if<var1> STRGREATER <var2> or if<var1> STREQUAL <var2> 字母表顺序比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 示例一
if(WIN32)
message(STATUS "This is windows.")
else()
message(STATUS "This is not windows.")
endif()

// 示例二
if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")
message(FATAL_ERROR "
FATAL: In-source builds are not allowed.
You should create a separate directory for build files.
")
endif()
while endwhile

功能:循环操作

1
2
3
4
5
while(<condition>)
COMMAND1(<ARGS> …)
COMMAND2(<ARGS> …)

endwhile(<condition>)
option

功能:控制某个变量或宏的定义与否,选项ON/OFF,如果变量在之前没有定义过,则在执行option时定义该变量

1
2
3
4
5
6
7
8
9
10
# 编译脚本传入宏定义 TEST_DEBUG
#!/bin/sh
cmake -DTEST_DEBUG=ON .
cmake --build .

# CMakeLists接受TEST_DEBUG 并设置默认关闭OFF
option(TEST_DEBUG "option for debug" OFF)
if (TEST_DEBUG)
add_definitions(-DTEST_DEBUG)
endif()

参考资料:

https://www.bilibili.com/video/BV17J411m7o1

https://www.jianshu.com/p/9763cd8325bf

https://www.jianshu.com/p/035bc18f8f62