基于VSCode和CMake进行C/C++开发(Linux)

基于VSCode和CMake进行C/C++开发(Linux)

1. Linux系统介绍

Linux是开源的操作系统。

  • Windows:单用户、多任务;Linux:多用户、多任务。
  • Linux:一切皆文件。
  • 文件操作:创建、保存、关闭、重命名、删除、恢复。

1.1 目录结构

  • Bin:binary(二进制)。存储一些二进制文件,文件都是可以运行的。
  • Dev:存放外接设备–盘、其他光盘等。其内的外接设备不能直接被使用,需要挂载–类似Windows下的分配盘符。
  • Etc:存储一些配置文件。
  • Home:表示除了root用户之外其他用户的家目录,类似Windows下的User目录。
  • Proc:process(进程)。存储Linux运行时的进程。
  • Root:root用户自己的家目录。

1.2 指令及选项

  • 指令格式:

    • 完整指令的标准格式:命令(空格)[选项](空格)[操作对象]
    • 选项及操作对象都可以没有,也可以是多个。
    # 指令示例:以下两条指令等价
    ls -l -a -h /home ./
    ls -lah /home ./
    

1.3 重要指令讲解

Ctrl+Alt+t打开终端

  • pwd-print current working directory

    • 打印当前终端所在目录

    • 语法:pwd

      pwd
      
  • ls-list directory contents

    • 列出当前工作目录下的所有文件名称

    • 语法:ls;ls [路径];ls [选项]

      # 1.省略路径
      ls
      # 2.相对路径
      ls ./       //当前目录
      ls ../      //上一级目录
      # 3.绝对路径
      ls /home
      # 4.加选项
      # -l:list,以详细列表的形式展示----显示文件中:d开头为文件夹,-开头为文件
      ls -l /home  
      # -a:显示所有的文件,包括隐藏文件
      ls -a /home  
      # -h:以可读性较高的形式显示
      ls -h /home  
      
  • cd-change directory

    • 切换当前工作目录

    • 语法:cd;cd ~;cd [路径]

      # 1.以下两条等价:进入当前用户的家目录下
      cd
      cd ~
      # 2.相对路径
      cd ..    //上级目录
      cd ../local   //上级目录的local目录
      # 3.绝对路径
      cd /user/local
      
  • mkdir-make directories

    • 创建目录

    • 语法:mkdir 路径;mkdir -p 路径;mkdir 路径1 路径2

      # 1.在当前路径下创建目录
      mkdir myfolder
      # 2.一次性创建多层不存在的目录
      mkdir -p a/b/c
      # 3.一次性创建多个目录:在当前目录分别创建a,b,c三个文件夹
      mkdir a b c
      
  • touch-change file timestamps

    • 创建新文件

    • 语法:touch [路径];touch 路径

      # 1.相对路径
      在当前目录下创建linux.txt文件
      touch linux.txt
      # 在上级目录下创建linux文件
      touch ../linux
      # 2.绝对路径
      touch /home/yy137/myfile
      # 3.在当前目录下创建file,file.txt两个文件
      touch file file.txt
      
  • rm-remove files or directories

    • 删除文件or目录

    • 语法:rm [选项] 路径;rm [选项] 目录

      # 1.相对路径
      # 删除当前路径下的文件
      rm myfile
      # 2.绝对路径
      rm /user/myfile
      # 3.删除当前路径下的目录
      rm -rf myfolder
      rm -rf /user/myfolder
      
  • cp-copy files and directories

    • 复制文件/文件夹到指定的位置

    • 语法:cp 被复制的文件路径 目标路径;cp -r 被复制的文件夹路径 目标路径;

      # 1.复制一个文件
      cp /home/yy137/myfile ./
      
      # 2.复制一个文件夹
      cp -r /home/yy137/myfolder / 
      
  • mv-move (rename) files

    • 移动文件到新的位置,或重命名文件

    • 语法:mv 需要移动的文件路径

      # 1.移动当前目录的文件到根目录下
      mv myfile /myfile
      # 2.移动当前目录的myfolder文件夹到根目录下
      mv myfolder /myfolder
      # 3.移动当前目录的myfile文件到根目录下,并重命名为myfile007
      mv myfile /myfile007
      
  • man-an interface to the system reference manuals

    • 包含了Linux中全部命令手册:查看命令使用手册,按q退出。

    • 语法:man[命令]

      man ls
      man man
      # 显示没有,可输入help cd查看
      man cd
      
  • reboot-reboot the machine

    • 重启Linux系统

    • 语法:reboot

      # 立即重启
      reboot
      
  • shutdown-power-off the machine

    • 关机

    • 语法:shutdown -h [时间]

      # 立即关机
      shutdown -h now
      

1.4 文件编辑

  • vim [file]

    • 所有的Linux系统都会内建Vim编辑器。
    • Vim是所有Unix及Linux系统下标准的编辑器。
    • Vim具有程序开发的能力,也可以用来对文件进行简单的编辑。
    # 安装成功确认
    touch test.txtx
    # vim打开文件
    vim test.txt
    # 编辑文件:i切换到插入模式;Esc + :保存
    '''''''''
    
  • gedit [file]

    • Linux下的一个纯文本编辑器。
    • 可以根据不同的语言高亮显现关键字和标识符。
    gedit test.txt
    
  • nano [file]

    • nano是一个小巧的文本编辑器。
    • 比vim简单得多,适合Linux初学者。
    • 某些Linux发行版的默认编辑器就是nano。
    nano test.txt
    

2. 开发环境搭建

2.1 编译器、调试器安装

  • 安装gcc、gdb

    # 安装软件之前更新:sudo--switch to user, apt--软件包
    sudo apt update
    sudo apt install bulid-essential gdb
    
    # 安装成功确认
    gcc --versio
    g++ --version
    gdb --version
    

2.2 CMake安装

sudo apt install cmake
cmake --version

3. GCC编译器

VSCode通过调用gcc编译器实现C/C++编译工作.

实际使用中:

  • 使用gcc指令编译C代码
  • 使用g++指令编译C++代码

3.1 编译过程

  1. 预处理Pre-Processing(.i文件)

    # -E选项指示编译器仅对输入文件进行预处理
    g++ -E test.cpp -o test.i   //生成.i文件
    
  2. 编译Compiling(.s文件)

    # -S选项告诉g++在为C++代码产生汇编语言文件后停止编译
    g++ -S test.i -o test.s    //生成.s文件
    
  3. 汇编Assembling(.o文件)

    # -c选项告诉g++仅把源代码编译为机器语言的目标代码
    g++ -c test.s -o test.o
    
  4. 链接Linking(bin文件)

    # -o选项为可执行文件用指定的文件名
    g++ test.o -o test
    
  5. 上述四步合并(cpp->bin)

    g++ test.cpp -o test
    ./test    //在当前目录下运行可执行文件test
    

3.2 g++重要编译参数

  • -g 编译待调试信息的可执行文件

    #  -g选项告诉gcc产生能被GNU调试器gdb使用的调试信息,以调试程序。
    # 产生带调试信息的可执行文件test
    g++ -g test.cpp -o test
    
  • -O[n] 优化源代码

    # 优化:省略代码中从未使用过的变量、直接将常量表达式用结果值代替等,
    # 这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。
    # -O0,不做优化
    # -O,等价于-O1,默认优化:同时减小代码的长度和执行时间
    # -O2,在-O1基础上,进行一些额外的调整工作,如指令调整等
    # -O3,包括循环展开和其他一些与处理特性相关的优化工作。
    # 会使编译速度变慢,但产生的代码通常执行速度更快。
    g++ -O2 test.cpp
    
  • -l 和 -L 指定库文件|库文件路径

    • -l指定程序要连接的库,-l后面紧跟库名
    • /lib,/usr/lib,/usr/local/lib中的库直接用-l参数就可以链接
    • 若库文件不在上面三个目录中,需要用-L指定库所在目录
    # 链接glog库
    g++ -lglog test.cpp
    
    # 链接myTest库
    g++ -L/home/yy137/myFolder -lmyTest test.cpp
    
  • -I (大写i)指定头文件搜索目录

    • /usr/include目录不需要指定。

    • 其他目录需要用-I指定

      g++ -I/myinclude test.cpp
      
  • -Wall 打印警告信息

g++ -Wall test.cpp
  • -w 关闭警告信息
g++ -w test.cpp
  • -std=c++11 设置编译标准
g++ -std=c++11 test.cpp
  • -o 指定输出文件名
g++ test.cpp -o test
  • -D 定义宏
#include<stdio.h>

int main() {
    #ifdef DEBUG
    	printf("DEBUG LOG\n");
    #endif
    	printf("in\n");
}
//在编译时,使用gcc -DDEBUG main.cpp,第7行代码可以被执行

3.3 g++命令行编译

3.3.1 直接编译
  • 最简单的编译,并运行

    # 将main.cpp src/Swap.cpp编译为可执行文件a.out(未重命名,系统自动命名)
    # Swap.cpp中的#include"Swap.h"在include文件夹下,所以-Iinclude
    g++ main.cpp src/Swap.cpp -Iinclude
    # 运行a.out
    ./a.out
    
  • 增加参数编译,并运行

    # 将main.cpp src/Swap.cpp编译为可执行文件b.out,附带参数
    g++ main.cpp src/Swap.cpp -Iinclude -std=c++11 -O2 -Wall -o b.out
    # 运行b.out
    ./b.out
    
3.3.2 生成库文件并编译

在Linux中,库文件分为静态库和共享库(动态库)两种。

静态库以.a为后缀名,共享库以.so结尾。

所有库都是一些函数打包后的集合。

  • 链接静态库生成可执行文件

    # 进入src目录下
    cd src
    
    # 汇编,生成Swap.o文件。-c:生成.o的二进制文件
    g++ Swap.cpp -c -I../include
    
    # 生成静态库libSwap.a:将Swap.o归档为libSwap.a的一个静态库文件
    ar rs libSwap.a Swap.o
    
    # 回到上级目录
    cd ..
    
    # 链接静态库,生成static_main可执行文件
    g++ main.cpp -lswap -Lsrc -Iinclude -o static_main
    
    # 执行static_main
    ./static_main
    
  • 链接动态库生成可执行文件

    # 进入src目录下
    cd src
    
    # 生成动态库libSwap.so。Swap.h在include目录下。-shared生成共享库(动态库)
    # PIC:position independent code,不加fPIC编译出来的.so文件需要再加载时重定位,无法真正共享
    g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so
    # 上面命令等价于以下两条命令
    g++ Swap.cpp -I../include -c -fPIC
    g++ -shared -o libSwap.so Swap.o
    
    # 回到上级目录
    cd ..
    
    # 链接,生成可执行文件sharemain
    g++ main.cpp -Iinclude -Lsrc -Swap -o sharemain
    
3.3.3 运行可执行文件
  • 静态库–相对路径

    # 运行可执行文件
    ./staticmain
    
  • 动态库–绝对路径

    # 运行可执行文件
    LD_LIBRARY_PATH=src ./sharemain
    

4. GDB调试器

GDB(GNU Debugger)是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发C/C++最常用的调试器。

VSCode是通过调用GDB调试器实现C/C++的调试工作的。

Windows系统中常见的集成开发环境(IDE),如VS、VC等,他们内部已经嵌套了相应的调试器。

4.1 常用调试命令参数

调试开始:执行**gdb[exefilename]**进入gdb调试程序,其中exefilename为要调试的可执行文件名。

# 括号中为简化使用
help(h) # 查看命令帮助,具体命令查询为;help+命令
run(r) # 重新运行文件,run-text:加载文本文件  run-bin:加载二进制文件
start #单步执行
list(l) # 查看原代码,list-n:从第n行查看代码  list+函数名:查看具体函数
set # 设置变量的值
next(n) # 单步调试:逐过程
step(s) # 单步调试:逐语句
backtrace(bt) # 查看函数调用的栈帧和层级关系
frame(f) # 切换函数栈帧
info(i) # 查看函数内部局部变量的数值
finish # 结束当前函数,返回到函数调用点
continue(c) # 继续运行
print(p) # 打印值及地址
quit(q) # 退出gdb
break+num(b) # 在第num行设置断点
info breakpoints # 查看当前设置的所有断点
delete breakpoints num(d) # 删除第num个断点
display # 追踪查看具体变量值
undisplay # 取消追踪观察变量
watch # 被设置观察点的变量发生修改时,打印显示
i watch # 显示观察点
enable breakpoints # 启用断点
disable breakpoints # 禁用断点

tips:

  1. 编译程序时需要加上-g,之后才能用gdb调试:gcc -g main.c -o main
  2. 回车键:重复上一命令

4.2 命令行调试

实战:调试以下代码

#include<iostream>
using namespace std;

int main(int argc, char ** argv) {
    int N = 100;
    int sum = 0;
    int i = 1;
    
    //calculate sum from 1 to 100
    while(i <= N) {
        sum += i;
        i++;
    }
    
    cout << "sum = " << sum << endl;
    cout << "The program is over." << endl;
    
    return 0;
}
  • 生成可执行文件
$ g++ sum.cpp -o sum_no_g
$ g++ -g sum.cpp -o sum_yes_g # 加-g,可以用gdb调试
$ ls
sum.cpp  sum_no_g  sum_yes_g
# sum_yes_g比sum_no_g文件大很多:里面包含编译信息
$ ls -l
total 48
-rw-rw-r-- 1 yy137 yy137   299 Apr 27 23:00 sum.cpp
-rwxrwxr-x 1 yy137 yy137  8968 Apr 27 23:02 sum_no_g
-rwxrwxr-x 1 yy137 yy137 29000 Apr 27 23:03 sum_yes_g
$ ls -lh
total 48K
-rw-rw-r-- 1 yy137 yy137  299 Apr 27 23:00 sum.cpp
-rwxrwxr-x 1 yy137 yy137 8.8K Apr 27 23:02 sum_no_g
-rwxrwxr-x 1 yy137 yy137  29K Apr 27 23:03 sum_yes_g
  • 进入gdb调试
$ gdb sum_yes_g
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from sum_yes_g...done.
  • 加断点
# 运行
(gdb) run

# 加断点:在13、14行加断点
(gdb) break 13
(gdb) b 14

# 查看断点
(gdb) info breakpoints
(gdb) i b

# 退出gdb
(gdb) quit

  • 调试实战
# 进入gdb
$ gdb sum_yes_g

# 加断点
(gdb) b 13
(gdb) i b

# 运行
(gdb) r   # 或 run

# 打印变量
(gdb) p i # 或 p i

# 继续执行
(gdb) continue

# 追踪变量i
(gdb) display i
(gdb) display sum

# 查看源代码
(gdb) list # 或l

5. IDE-VSCode

5.4 项目实战

  • include/swap.h

    #pragma once
    
    #include<iostream>
    
    class swap {
    public:
        swap(int a, int b) {
            this->_a = a;
            this->_b = b;
        }
        void run();
        void printInfo();
    private:
        int _a;
        int _b;
    };
    
  • src/swap.cpp

    #include"swap.h"
    
    void swap::run() {
        int tmp;
        tmp = _a;
        _a = _b;
        _b = tmp;
    }
    
    void swap::printInfo() {
        std::cout << "_a = " << _a << std::endl;
        std::cout << "_b = " << _b << std::endl;
    }
    
  • main.cpp

    #include"swap.h"
    
    int main(int argc, char **argv) {
        swap mySwap(10, 20);
        mySwap.printInfo();
        mySwap.run();
        mySwap.printInfo();
    
        return 0;
    }
    
  • terminal运行

    g++ main.cpp src/swap.cpp -Iinclude -o main
    ./main
    

6. CMake

Cmake是一个跨平台的安装编译工具。

Cmake可以用简单的语句来描述所有平台的安装(编译过程)。

6.1 Cross-platform development

6.2 语法特性

  • 基本语法格式:指令(参数1 参数2 参数3 …)

    • 参数在括号中
    • 各参数之间使用空格或分号隔开
  • 指令大小写无关,变量和参数大小写相关

    set(HELLO hello.cpp)
    add_executable(hello main.cpp hello.cpp)
    ADD_EXECUTABLE(hello main.cpp ${HELLO}) # 变量使用${}方式取值,但在if控制语句中直接使用变量名:if(HELLO)
    

6.3 重要指令和常用变量

6.3.1 重要指令
  • cmake_minimum_required:指定Cmake的最小版本要求

    # cmake_minimum_required(VERSION versionNumber[FETAL_ERROR])
    cmake_minimum_required(VERSION 2.8.3)
    
  • project:定义工程名称,并可指定工程支持的语言

    # project(projectName [CXX][C][JAVA]) #[]表示可选项,可以没有
    project(HELLOWORLD) # 工程名称为HELLOWORLD
    
  • set:显式的定义变量

    # set(VAR[VALUE][CACHE TYPE DOCSTRING [FORCE]])
    set(SRC sayhello.cpp hello.cpp) # 定义SRC变量,其值为sayhello.cpp hello.cpp
    
  • include_directories:向工程添加多个特定的头文件搜索路径(相当于指定g++编译器的-I参数)

    # include_directories([AFTER|BEFORE][SYSTEM]DIR1 DIR2 ...)
    # 将/usr/include/myincludefolder和./inclue添加到头文件搜索路径
    include_directories(/usr/include/myincludefolder ./inclue) 
    
  • link_directories:向工程添加多个特定的库文件搜索路径(相对于指定g++编译器的-L参数)

    # link_direatories(dir1 dir2 ...)
    # 将/usr/lib/mylibfolder和./lib添加到库文件搜索路径
    link_directories(/usr/lib/mylibfolder ./lib)
    
  • add_library:生成库文件

    # add_library(libname[SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ...)
    add_library(hello SHARED ${SRC}) #通过SRC变量生成libhello.so共享库
    
  • add_compile_options:添加编译参数

    # add_compile_options(<option>...)
    # 添加编译参数 -wall -std=c++11
    add_compile_options(-wall -std=c++11 -o2)
    
  • add_executable:生成可执行文件

    # add_executable(exename source1 source2 source3 ...)
    # 编译main.cpp生成可执行文件main
    add_executable(main main.cpp)
    
  • target_link_libraries:为target添加需要链接的共享库(相对于指定g++编译器-l参数)

    # target_link_libraries(target library1<debug|optimized> library2 ...)
    # 将hello动态库文件链接到可执行文件main
    target_link_libraries(main hello)
    
  • add_subdirectory:向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置

    # add_subdirectory(source_dir[binary_dir][EXCLUDE_FROM_ALL])
    # 添加src子目录,src中需有一个CMakeLists.txt
    add_subdirectory(src)
    
  • aux_source_directory:发现一个目录下所有的源文件,并将列表存储在一个变量中;这个指令临时被用来自动构建源文件列表。

    # aux_source_directory(dir VARIABLE)
    # 定义SRC变量,其值为当前目录下所有的源代码文件
    aux_source_directory(. SRC)
    # 编译SRC变量所代表的源文件,生成main可执行文件
    add_executable(main ${SRC})
    
6.3.2 Cmake常用变量
  • CMAKE_C_FLAGS:gcc编译选项

  • CMAKE_CXX_FLAGS:g++编译选项

    # 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    
  • CMAKE_BUILD_TYPE:编译类型(Debug,Release)

    # 设定编译类型为debug,调试时选择debug
    set(CMAKE_BUILD_TYPE Debug)
    # 设定编译类型为release,发布时选择release
    set(CMAKE_BUILD_TYPE Release)
    
  • CMAKE_BINARY_DIR

    PROJECT_BINARY_DIR

    _BINARY_DIR

    上述三个变量指代内容是一致的。

    1. in source build,指工程顶层目录。
    2. out-of-source,指工程编译发生的目录。
    3. 第2个和1、3有细微差别。
  • CMAKE_SOURCE_DIR

    PROJECT_SOURCE_DIR

    _SOURCE_DIR

    上述三个变量指代内容一致,都是工程顶层目录,与编译方式无关。

    2与1、3稍有差别。

6.4 Cmake编译工程

CMake目录结构:项目主目录存在一个CmakeLists.txt文件。

两种方式设置编译规则:

  1. 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录。
  2. 包含源文件的子文件夹不包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中。
6.4.1 编译流程

在Linux平台下使用Cmake构建C/C++工程的流程如下:

  1. 手动编写CMakeLists.txt。
  2. 执行命令cmake PATH生成Makefile(PATH是顶层CMakeLists.txt所在的目录)。
  3. 执行命令make进行编译。
# important tips
.       # 表示当前目录
./      # 表示当前目录

..      # 表示上级目录
../     # 表示上级目录
6.4.2 两种构建方式
  • 内部构建in-source build:不推荐使用

    内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。

  • 外部构建out-of-source build:推荐使用

    将编译输出文件与源文件放到不同目录中

    # 1. 在当前目录下,创建build文件夹
    mkdir build
    # 2. 进入到build文件夹
    cd build
    # 3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件
    cmake ..
    # 4. 执行make命令,生成target
    make
    

6.5 Cmake代码实践

针对第5节的工程。

6.5.1 最小CMake工程
  • CMakeLists.txt
# 设置最低要求版本
cmake_minimum_required(VERSION 3.0)
# 设置工程名称TEST
project(TEST)
# 生成可执行文件test_make
add_executable(test_cmake test.cpp) 
  • terminal下执行
cmake .
  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值