Linux上使用C++
Qianming Huang Three

GCC编译器

gcc可以用于编译C代码(g用于编译C代码)

编译过程

image

原始的 test.cpp 文件

  1. 预处理:生成.i文件

    在这一步骤中,编译器会对宏定义、常量进行处理,比如对常量进行替换操作。

    1
    2
    # -E 表示仅进行预处理。
    g++ -E test.cpp -o test.i
    image

    CONST_NUM被替换成了40,同时前面的iostream库也被导入近来,所以会突增到28650行。

  2. 编译:生成.s文件

    将代码转化为汇编语言。

    1
    2
    # -S 表示仅进行汇编
    g++ -S test.i -o test.s
    image

    汇编程序。

  3. 汇编:生成.o文件

    将汇编代码转化为计算机能理解的二进制代码。

    1
    2
    # -C 表示仅进行汇编
    g++ -c test.s -o test.o
    image

    二进制代码。

  4. 链接:生成可执行的二进制程序。

    将系统中的库链接到当前程序中。

    1
    2
    # -o 将可执行文件命名为指定的名字
    g++ test.o -o test

在实际使用过程中,可以直接使用下面的这一步。其隐式的包含了前面四步。

1
g++ test.cpp -o test

g++编译参数

  • -g编译带调试信息的可执行文件。这意味着代码可以被GDB调试

    1
    g++ -g test.cpp -o test
  • -O[n]优化源代码。编译器会对代码进行优化,比如去除没用过的变量。提高可执行文件的运行速度。

    一般情况下-O2就已经足够了。-O-O1都是初步优化。

  • -l-L都用于指定链接库。

    -l为库名。

    -L为库的位置。

    默认情况下,程序都是在/lib/usr/lib/usr/local/lib中寻找库,此时无需参数-L

    1
    g++ -lOfiicialLib test.cpp # 链接的库为OfficialLib

    但如果是自己写的库,需要指定位置,那么就需要使用-L

    1
    g++ -L/home/abc/MyLibFolder -lMyLib test.cpp
  • -I指定头文件位置。

    1
    g++ test.cpp -Iinclude # include下保存了test.cpp中需要的.h文件

g++多文件编译

image

文件结构如图所示。

  • 最简单直接的方式

1
2
# 最简单的方式
g++ main.cpp src/swap.cpp -Iinclude -o result.out # -I指明了头文件所在文件夹
  • 生成库后再运行

    • 静态库版本:将src.cpp生成一个静态库,随后再链接到main.cpp上。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      cd src # 在src文件夹下
      #在./src/下
      #开始生成静态库
      g++ swap.cpp -c -I../include # -c表示生成一个二进制文件 -I制定了swap.h中头文件所在位置
      # 随后,会输出一个swap.o的二进制文件
      ar rs libSwap.a swap.o # 将二进制文件归档为静态库文件libSwap.a
      # 随后,会输出一个lisSwap.a
      # 此时 src内保存了:libSwap.a swap.cpp swap.o

      cd .. # 回到核心目录下
      # 此处在./文件夹下
      # 链接静态库
      g++ main.cpp -Iinclude -Lsrc -lSwap -o static_main.out # 注意,这里的-l后面写的是Swap是因为,-l操作会自动的在后接上的内容(Swap)前加lib后加.a。此时-l会去寻找libSwap.a
      image
    • 动态库版本:

      1
      2
      3
      4
      5
      6
      7
      8
      cd src
      #在./src下进行
      g++ swap.cpp -I../include -fPIC -shared -o libSwap.so # -fPIC 表示生成与位置无关文件,动态链接库必备。 -shared表示生成动态链接库

      cd ../
      #在./下进行
      #链接动态库
      g++ main.cpp -Iinclude -Lsrc -lSwap -o share_main.out # 同理-l会加上东西,但是编译器会优先尝试动态库(libSwap.so)
      image

GDB调试器

在Vscode中调试代码

image

点击图片中的”创建launch.json文件"

launch.json文件的编写如下

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
// launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "my_cmake_exe",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/my_cmake_exe", //可执行文件所在位置
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "Build" // 会调用tasks.json中相对的指令,完成自动编译
}
]
}

注意修改program选项,指定为my_cmake_exe。同时,在根目录的CMakeLists.txt中应该编写set(CMAKE_BUILD_TYPE Debug),并且确保set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")中没有设置-O2选项,避免因为优化代码,导致调试的不准确。

随后配置tasks.json文件。

image

点击“配置默认生成任务”,生成一个tasks.json

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
31
32
33
34
// tasks.json
{
"version": "2.0.0",
"options": {
"cwd": "${workspaceFolder}/build" // 相当于 cd ${workspaceFolder}/build
},
"tasks": [
{
"type": "shell",
"label": "cmake", // 这一部分的名称
"command": "cmake", // 这一部分要执行的命令
"args": [
".."
]
},
{
"label": "make",
"group": {
"kind": "build",
"isDefault": true
},
"command": "make",
"args": []
},
{
"label": "Build", // 这个lable应该被输入到launch.json
"dependsOrder": "sequence", // 按照列出的顺序执行依赖
"dependsOn": [ //依赖项的名字就是前面的label
"cmake",
"make"
]
}
]
}

随后,无需再手动编译,直接执行调试就可以对程序自动的进行编译

再次展示文件结构。

image

CMake

跨平台的安装编译工具,可以用于描述所有平台编译过程。没有Cmake,你就需要给每一个平台都写一个工程构建文件(比如Makefile、Xcode等)。有了Cmake,你只需要与Cmake打交道就可以了,只需要修改CMakeList.txt。

基本语法

1
2
3
4
5
指令(参数1 参数2 参数3)
# 举例
set(HELLO hello.cpp)
add_executable(hello main.cpp ${HELLO})
IF(HELLO) # IF(${HELLO})是错误的!!!
  • 参数之间需要用空格分号分开,逗号用不了。

  • 指定与大小写无关,参数和变量是大小写相关的

  • ${}用于变量取值。在IF语句中可以直接使用变量名。

常用指令

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
31
32
33
34
cmake_minimum_required(VERSION 2.8.3) # 设置CMake最小版本号为2.8.3

project(HELLOWORLD) # 定义工程名

#显式定义变量
set(SRC hello.cpp Nihao.cpp) # 定义SRC变量并设置为hello.cpp Nihao.cpp

# 添加头文件搜索路径 类似g++的-I
include_directories(/usr/include ./include) # 将/usr/include ./include 两个文件夹加入到头文件搜索路径

# 添加库文件搜索路径 类似g++的-L
link_directories(/usr/lib ./lib) # 将/usr/lib ./lib 两个文件夹加入到库文件搜索路径

# 生成库文件
add_library(hello SHARED ${SRC}) # 从SRC取值,生成一个叫hello的动态库,最终生成的文件是libhello.so库文件
add_library(hello STATIC ${SRC}) # 生成静态库

# 增加编译选项
add_compile_options(-Wall -std=c++11 -o2) # 编译的时候追加-Wall -std=c++11 -o2这三个命令

#生成可执行文件
add_executable(main main.cpp) # 将main.cpp生成main的可执行文件
add_executable(exename source1 source2 sourceN) # 标准语法

# 添加动态链接库 类似g++的-l
target_link_libraries(main hello) # 将hello这个库链接到main上

# 向工程中添加存放源文件的子目录
add_subdirectory(src) # 添加src子目录,需要注意的是子目录下必须要有一个CMakeLists.txt!!,哪怕是空的

# 给目录内所有的源文件取一个变量名
aux_source_directory(dir VARIABLE) # 标准语法
aux_source_directory(. SRC) # 案例,将当前目录下所有的源代码取名为SRC
add__executable(main ${SRC}) # 将SRC代指的所有源文件生成可执行文件

常用变量

1
2
3
4
5
6
7
8
9
CMAKE_C_FLAGS # gcc编译选项
CMAKE_CXX_FLAGS # g++编译选项
# 举例,在编译选项后追加一些内容
set(CMAKE_CXX_FLAGS "{$CMAKE_CXX_FLAGS} -std=c++11")

CMAKE_BUILD_TYPE # 设置编译类型
# 举例
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_BUILD_TYPE Release)

编译流程

  1. 编写一个CMakeList.txt。

  2. 执行cmake PATH生成Makefile(linux下)。

    ​ PATH是顶层CMakeList.txt所在的目录,比如在主目录中直接编译写好的CMakeList.txt,就可以写cmake .

  3. 执行make进行编译

在编译过程中,有两种构建方式。第一种是内部构建,其意思是指直接在工程主目录下进行cmake指令,直接就是cmake .+make。第二种是外部编译,其含义为现创建一个build目录,进入到build目录后,在目录内部执行cmake ..的命令来编译位于上级目录中的CMakeList.txt,随后的make指令也在build目录中执行。