1.gcc

  gcc是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。
当你的程序只有一个源文件时,直接就可以用gcc命令编译它。

  1. gcc hello.c -o hello,也可以gcc -o hello hello.c
  2. 编译成功后会生成一个可执行文件hello,在命令行中输入 ./hello运行程序

或者分步骤

1
2
3
4
5
6
gcc - 选项 源文件名(依赖文件) -o 目标文件名
或者
gcc -选项 -o 目标文件 源文件名(依赖文件)
选项:
-E 从当前文件开始,在预处理完成之后停止, 生成的文件后缀一般加i
-S 从当前文件开始,在编译完成之后停止, 生成的文件后缀一般加s
  1. 预处理:gcc -E hello.c -o hello.i
  2. 编译:gcc -S hello.i -o hello.s
  3. 汇编:gcc -c hello.s -o hello.o
  4. 链接:gcc hello.o -o hello

但是当你的程序包含很多个源文件时,用gcc命令逐个去编译时,就很容易混乱而且工作量大。

2.make和makefile

2.1.make

make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式,通过调用makefile文件中用户指定的命令来进行编译和链接的。
make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。
makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。
makefile在一些简单的工程完全可以人工手下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改。

2.2.makefile语法

一个简单的makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# C++ 简单项目的 Makefile 示例

# 编译器
CXX = g++
# 编译选项
CXXFLAGS = -Wall -std=c++17
# 源文件
SRC = main.cpp utils.cpp
# 对应的目标文件(.o)
OBJ = $(SRC:.cpp=.o)
# 最终生成的可执行文件名
TARGET = my_program

# 默认目标
all: $(TARGET)

# 链接目标文件生成可执行文件
$(TARGET): $(OBJ)
$(CXX) $(OBJ) -o $(TARGET)

# 编译每一个 .cpp 文件为 .o 文件
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $<

# 清理命令:删除中间文件和可执行文件
clean:
rm -f $(OBJ) $(TARGET)


2.2.1.基础结构

1
2
目标: 依赖1 依赖2 ...
[TAB]命令
  • 目标(Target):通常是你想生成的文件,比如可执行文件 main。
  • 依赖(Dependencies):是生成目标所依赖的文件,比如源代码文件 .cpp。
  • 命令(Command):是生成目标所需要执行的 shell 命令(注意前面要用Tab,不是空格!)

2.2.2.makefile变量

  1. 自定义变量
    在makefile文件中定义的变量
    make工具传给makefile的变量

    1
    2
    3
    4
    5
    6
    定义变量:
    变量名=变量值
    引用变量:
    $(变量名)

    ${变量名}

    注意:

    • 变量是大小写敏感的
    • 变量一般都在makefile的头部定义
    • 变量几乎可在makefile的任何地方使用
    • makefile变量名可以以数字开头
  2. 简单(即时)变量
    ​ 变量的值即刻确定,在定义的时候就已经确定了。

  3. 延时变量
    ​变量要使用到的的时候才会确定,在定义等于时并不存在
    常用的变量的定义如下:

    1
    2
    3
    4
    5
    :=      # 即时变量
    = # 延时变量
    ?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
    \+= # 附加, 它是即时变量还是延时变量取决于前面的定义
    ?=: # 如果这个变量在前面已经被定义了,这句话就会不会起效果,
  4. 系统环境变量
    make工具解析makefile前,读取系统环境变量并设置为makefile的变量

  5. 预定义变量(自动变量)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @目标名∗∗∗∗< 依赖文件列表中第一个文件
    $^ 依赖文件列表中除去重复文件的部分
    CC C编译器的名称,默认值为cc
    CFLAGS C编译器的选项

    AR 归档维护程序的程序名,默认值为ar
    ARFLAGS 归档维护程序的选项
    AS 汇编程序的名称,默认值为as
    ASFLAGS 汇编程序的选项
    CPP C预编译器的名称,默认值为$(CC)-E
    CPPFLAGS C预编译器的选项
    CXX C++编译器的名称,默认值为g++
    CXXFLAGS C++编译器的选项
    LIBS:告诉链接器要链接哪些库文件
    LDFLAGS:gcc 等编译器会用到的一些优化参数,也可以在里面指定库文件的位置。
    CFLAGS: 指定头文件(.h文件)的路径
    CFLAGS部分参数如下:
    -S 只是编译不汇编,生成汇编代码
    -E 只进行预编译,不做其他处理
    -g 在可执行程序中包含标准调试信息
    -o file 把输出文件输出到file里
    -v 打印出编译器内部编译各过程的命令行信息和编译器的版本
    -I dir 在头文件的搜索路径列表中添加dir目录
    -L dir 在库文件的搜索路径列表中添加dir目录
    -static 链接静态库
    -llibrary 连接名为library的库文件

2.2.3.通配符

1
2
3
4
5
6
7
%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标文件
$<:表示第1个依赖文件
$^:表示所有依赖文件
$*: 表示目标文件的名称,不包含扩展名
$?: 依赖项中,所有比目标文件新的依赖文件

2.2.4.伪目标

一些目标并不是真正的文件,而是操作,比如 clean

2.2.5.函数

在Makefile中可以使用它内部的函数来处理文本,从而让我们的命令或规则更加智能。函数调用之后,函数的返回值可以当作变量来使用。

  • 函数的调用语法
    1
    2
    3
    $(<function><arguments>)     
    或者
    ${<function><arguments>}
  • 字符串替换与分析函数patsubst
    函数 patsubst 语法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $(patsubst <pattern>,<replacement>,<text>)
    查找text中以空白符分隔的单词是否符合模式,如果匹配的话,就可以使用进行替换。
    实例
    files2 = a.c b.c c.c d.c e.c abc
    dep_files = $(patsubst %.c,%.d,$(files2))

    all:
    @echo dep_files = $(dep_files)

    结果dep_files = a.d b.d c.d d.d e.d abc
  • findstring
    函数 findstring 语法如下:
    1
    2
    $(findstring <FIND>,<IN>)
    功能:从字符串中查找指定的字符串,找到就返回,没找到返回空。
  • filter、filter-out
    函数 filter 语法如下:
    1
    2
    $(filter <pattern...>,<text>)    # 在text中取出符合patten格式的值
    功能:以模式过滤 字符串中的内容,保留符合模式的内容,可有多个模式。
    函数filter-out 语法如下:
    1
    2
    $(filter-out <pattern...>,text)   # 在text中取出不符合patten格式的值
    功能:以模式过滤 字符串中的内容,保留不符合模式的内容,可有多个模式。
  • 文件名称处理函数wildcard
    函数Wildcard语法如下:
    1
    2
    $(wildcard <pattern...>) 
    功能:这个函数 wildcard 会以 这个格式,去寻找所有存在的文件,返回存在文件的名字,文件名以空格分隔。若不存在任何符合此格式的文件,则返回空
  • make控制函数

    1. info
      函数info语法如下:
      1
      2
      $(info <text>) 
      功能:这个函数会标准输出打印文本 ,用于输出调试信息。
    2. warning
      函数warning语法如下:
      1
      2
      $(warning <text>) 
      功能:这个函数会向标准输出打印文本 ,用于输出警告信息。make继续执行。
    3. error
      函数error语法如下:

      1
      2
      $(error <text>) 
      功能:这个函数会向标准错误输出打印文本 ,用于输出指明错误信息。make停止执行
    4. foreach
      函数foreach语法如下:

      1
      2
      $(foreach <var>,<list>,<text>) 
      功能:把参数中的单词逐一取出放到参数所指定的变量中,然后再执行 所包含的表达式。每一次 会返回一个字符串,循环过程中, 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,text所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

3.cmake和CmakeList.txt

Cmake就可以根据一个叫CMakeLists.txt文件makefile文件给上面那个make用。当然cmake还有其他功能,就是可以跨平台生成对应平台能用的makefile,你不用再自己去修改了。

分别使用makefile和CmakeLists.txt构建项目
在这里插入图片描述

3.1.CmakeLists.txt文件编写

编写CMakeLists.txt最常用的功能就是调用其他的头文件(.h .hpp )、动态链接库(.so)、静态链接库(.a),将源文件.cpp .c .cc 编译成目标可执行或目标可链接库文件。CMake是makefile的上层工具,用于跨平台构建环境,生成可移植的makefile,并简化自己动手写makefile时的巨大工作量。*

一般,项目工程文件中:

  • bin 文件夹存放 编译好的可执行二进制文件
  • include 文件夹存放头文件
  • src 文件夹存放源代码
  • lib 文件夹存放编译好的库文件或者要调用的库文件

CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意

通常一个CMakeLists.txt需按照下面的流程:

1
2
3
4
5
6
7
8
9
10
11
project(xxx)                                          #必须

add_subdirectory(子文件夹名称) #父目录必须,子目录不必

add_library(库文件名称 STATIC 文件) #通常子目录(二选一)
add_executable(可执行文件名称 文件) #通常父目录(二选一)

include_directories(路径) #必须
link_directories(路径) #必须

target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称) #必须

  • 注释
    CMake 使用 # 进行行注释,可以放在任何位置。
    CMake 使用 #[[ ]] 形式进行多行注释。

  • 变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    PROJECT_NAME:项目名称
    PROJECT_SOURCE_DIR:工程的根目录
    PROJECT_BINARY_DIR :执行cmake命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
    CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt文件所在目录
    CMAKE_CURRENT_BINARY_DIR:target 编译目录
    CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
    CMAKE_CURRENT_LIST_LINE:当前所在的行
    CMAKE_INSTALL_PREFIX:工程安装目录,所有生成和调用所需的可执行程序,库文件,头文件都会安装到该路径下,Unix/Linux下默认为/usr/local,windows下默认为C:\Program Files
    CMAKE_MODULE_PATH:设置搜索CMakeModules模块(.cmake)的额外路径,用来定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
    CMAKE_C_FLAGS:设置C编译选项
    CMAKE_CXX_FLAGS:设置C++编译选项
    CMAKE_C_COMPILER:设置C编译器
    CMAKE_CXX_COMPILER:设置C++编译器
    CMAKE_BUILD_TYPE:build类型(Debug,Release,…),CMAKE_BUILD_TYPE=Debug;
    CMAKE_COMMAND:也就是CMake可执行文件本身的全路径
    CMAKE_DEBUG_POSTFIX:Debug版本生成目标的后缀,通常可以设置为"d"字符
    CMAKE_GENERATOR:编译器名称,例如”UnixMakefiles”, “Visual Studio 7”等
    BUILD_SHARED_LIBS:指定编译成静态库还是动态库
    EXECUTABLE_OUTPUT_PATH:设置编译后可执行文件目录
    LIBRARY_OUTPUT_PATH:设置生成的库文件目录
    CMAKE_INCLUDE_CURRENT_DIR:自动添加CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到当前处理的CMakeLists.txt,set (CMAKE_INCLUDE_CURRENT_DIR ON)
    CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3
    CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4
    CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1
    CMAKE_SYSTEM:系统名称,比如 Linux-2.6.22
    CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
    CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
    CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686
    UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
    WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin
  • CMake变量使用${}方式取值(使用),但是在if控制语句中是直接使用变量名
    环境变量使用$ENV{}方式取值,使用SET(ENV{VAR} VALUE)赋值
  • cmake_minimum_required()指定使用的 cmake 的最低版本
  • project()定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

    1
    2
    3
    4
    5
    6
    project(<PROJECT-NAME> [<language-name>...])
    project(<PROJECT-NAME>
    [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
    [DESCRIPTION <project-description-string>]
    [HOMEPAGE_URL <url-string>]
    [LANGUAGES <language-name>...])
  • add_executable():定义工程会生成一个可执行程序
    add_executable(可执行程序名 源文件名称)

    • 这里的可执行程序名和project中的项目名没有任何关系
    • 源文件名可以是一个也可以是多个,如有多个可用空格或;间隔
      1
      add_executable(app add.c div.c main.c mult.c sub.c)
  • 假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在cmake里定义变量需要使用set。set定义变量

    1
    SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
    • VAR:变量名
    • VALUE:变量值
      1
      2
      3
      4
      5
      6
      # 方式1: 各个源文件之间使用空格间隔
      # set(SRC_LIST add.c div.c main.c mult.c sub.c)

      # 方式2: 各个源文件之间使用分号 ; 间隔
      set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
      add_executable(app ${SRC_LIST})
  • C++程序的时候,可能会用到C++11、C++14、C++17、C++20等新特性,那么就需要在编译的时候在编译命令中制定出要使用哪个标准,还是使用set
    1
    2
    3
    4
    5
    6
    #增加-std=c++11
    set(CMAKE_CXX_STANDARD 11)
    #增加-std=c++14
    set(CMAKE_CXX_STANDARD 14)
    #增加-std=c++17
    set(CMAKE_CXX_STANDARD 17)
  • 指定可执行程序的输出路径(EXECUTABLE_OUTPUT_PATH),通过set设定
    1
    2
    set(HOME /home/robin/Linux/Sort)
    set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:

    1
    aux_source_directory(< dir > < variable >)
    • dir:要搜索的目录
    • variable:将从dir目录下搜索到的源文件列表存储到该变量中

      例子

      1
      2
      3
      4
      5
      6
      cmake_minimum_required(VERSION 3.0)
      project(CALC)
      include_directories(${PROJECT_SOURCE_DIR}/include)
      # 搜索 src 目录下的源文件
      aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
      add_executable(app ${SRC_LIST})
  • get_filename_componet获取文件路径
  • file可以搜索文件(当然,除了搜索以外通过 file 还可以做其他事情)

    1
    file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
    • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
    • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。

      搜索当前目录的src目录下所有的源文件,并存储到变量中

      1
      2
      file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
      file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
  • 包含头文件,在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是include_directories

    1
    include_directories(headpath)
  • 制作静态库
    add_library(库名称 STATIC 源文件1 [源文件2] ...)
    在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
    例如:要将src目录中的源文件编译成静态库,然后再使用

    1
    2
    3
    4
    5
    cmake_minimum_required(VERSION 3.0)
    project(CALC)
    include_directories(${PROJECT_SOURCE_DIR}/include)
    file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
    add_library(calc STATIC ${SRC_LIST})

    这样最终就会生成对应的静态库文件libcalc.a

  • 制作动态库
    add_library(库名称 SHARED 源文件1 [源文件2] ...)
    在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

    1
    2
    3
    4
    5
    cmake_minimum_required(VERSION 3.0)
    project(CALC)
    include_directories(${PROJECT_SOURCE_DIR}/include)
    file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
    add_library(calc SHARED ${SRC_LIST})

    这样最终就会生成对应的动态库文件libcalc.so。

  • 指定动态库、静态库的输出路径
    通过set命令给EXECUTABLE_OUTPUT_PATH宏设置了一个路径
    由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。

  • 链接静态库
    link_libraries(<static lib> [<static lib>...])

    • 参数1:指定出要链接的静态库的名字,可以是全名 libxxx.a,也可以是掐头(lib)去尾(.a)之后的名字 xxx
    • 参数2-N:要链接的其它静态库的名字

      link_directories(<lib path>)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      $ tree 
      .
      ├── build
      ├── CMakeLists.txt
      ├── include
      │ └── head.h
      ├── lib
      │ └── libcalc.a # 制作出的静态库的名字
      └── src
      └── main.cpp
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      cmake_minimum_required(VERSION 3.0)
      project(CALC)
      # 搜索指定目录下源文件
      file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
      # 包含头文件路径
      include_directories(${PROJECT_SOURCE_DIR}/include)
      # 包含静态库路径
      link_directories(${PROJECT_SOURCE_DIR}/lib)
      # 链接静态库
      link_libraries(calc)
      add_executable(app ${SRC_LIST})
  • 链接动态库
    target_link_libraries

    1
    2
    3
    4
    target_link_libraries(
    <target>
    <PRIVATE|PUBLIC|INTERFACE> <item>...
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
    • target:指定加载动态库的文件的名字,(谁需要)
    • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
      1
      2
      3
      4
      5
      6
      7
      cmake_minimum_required(VERSION 3.0)
      project(TEST)
      file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
      # 添加并指定最终生成的可执行程序名
      add_executable(app ${SRC_LIST})
      # 指定可执行程序要链接的动态库名字
      target_link_libraries(app pthread)
      pthread、calc都是可执行程序app要链接的动态库的名字。
  • 日志message
    1
    message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
    • (无) :重要消息
    • STATUS :非重要消息
    • WARNING:CMake 警告, 会继续执行
    • AUTHOR_WARNING:CMake 警告 (dev), 会继续
    • SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
    • FATAL_ERROR:CMake 错误, 终止所有处理过程
  • 控制语句,条件判断if
    1
    2
    3
    4
    5
    6
    7
    if(<condition>)
    <commands>
    elseif(<condition>) # 可选快, 可以重复
    <commands>
    else() # 可选快
    <commands>
    endif()
  • 循环foreach
  • 循环while

3.2.例子

见下一篇文章