摘要:本系统基于CentOS7.6,x86_64环境下操作;关键词:Linux、VSCode、CMake、C/C++、GCC/GDB
适用范围:
想从Windows转Linux开发的者
想深入了解并掌握GCC编译器语法和规则者
想深入了解并掌握GDB调试器命令行调试者
想学习使用CMake构建C/C++工程者
想学习Linux下使用VSCode进行C/C++开发者
第一讲:Linux系统介绍
1.1目录结构
1.2指令与选项
1.3重要指令讲解
目录、文件创建、查看、编辑、修改、删除等
1.4文件编译(vim/vi)
第二讲 开发环境搭建
2.1编译器、调试器安装
为方便起见,以下命令行操作均在root(管理员)用户登录操作
安装GCC,GDB
[root@localhost ~]# yum update # 通过以下命令安装编译器和调试器 s[root@localhost ~]# yum -y install build-essential gdb
安装成功确认
以下命令确认每个软件是否安装成功,如果成功,则显示版本号 [root@localhost ~]#gcc --version gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44) [root@localhost ~]#g++ --version g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44) [root@localhost ~]#gdb --version GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
2.2CMake安装
安装cmake
# 通过以下命令安装编译器和调试器 [root@localhost ~]# yum -y install cmake
安装成功确认
# 确认是否安装成功 如果成功,则显示版本号 [root@localhost ~]# cmake --version cmake version 3.20.5 CMake suite maintained and supported by Kitware (kitware.com/cmake).
第三讲 GCC/g++编译器
前言:
1.GCC编译器支持编译GO、Objective-C、Objective-C++、Fortran等程序
2.Linux开发从C/C++一定要熟悉GCC
3.VSCode是通过GCC编译器来实现C/C++的编译工作的
实际使用中:
使用gcc指令编译C代码
使用g++指令编译C++ 代码
3.1编译过程
1.预处理-Pre=Processing //.i文件
# -E 选项指示编译器仅对输入文件进行预处理 [root@localhost ~]#g++ -E test.cpp -o test.i //.i文件
2.编译-Compiling //.s文件
-S 编译选项告诉 g++在为c++ 代码产生了汇编语言文件后停止编译 g++产生的汇编语言文件的缺省扩展名是.s [root@localhost ~]#g++ -S test.i -o test.s
缺省扩展名:任何文件都是有扩展名的,扩展名决定了文件的属性,缺省就是按系统默认的情况执行,(添加新建文件的扩展名,运行相关程序),通常也就不显示了,如果想知道扩展名,可以右击文件属性查看
3.汇编-Assembling // .o文件
# -c 选项告诉 g++ 仅把源代码编译为机器语言的目标代码 缺省时 g++ 建立的目标代码文件有一个 .o 的扩展名 [root@localhost ~]#g++ -c test.s -o test.o
4.链接-Linking //bin文件
# -o 编译选项来为将产生的可执行文件用指定的文件名 [root@localhost ~]#g++ test.o -o test
3.2 g++重要文件编译参数
1.-g 编译带调试信息的可执行文件
# -g 选项告诉 GCC 产生能被 GNU 调试器GDB使用的调试信息,以调试程序。 # 产生带调试信息的可执行文件test [root@localhost ~]#g++ -g test.cpp
2.-O[n] 优化源代码
#所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。 # -O 选项告诉 g++ 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉 g++ 产生尽可能小和尽可能快的代码。 如-O2,-O3,-On(n 常为0–3) # -O 同时减小代码的长度和执行时间,其效果等价于-O1 # -O0 表示不做优化 # -O1 为默认优化 # -O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。 # -O3 则包括循环展开和其他一些与处理特性相关的优化工作。 # 选项将使编译的速度比使用 -O 时慢, 但通常产生的代码执行速度会更快。 # 一般使用 -O2优化源代码,并输出可执行文件 [root@localhost ~]#g++ -O2 test.cpp
案例展示:未做优化和做优化的效果
1.文件大
未优化: [root@localhost LinuxC++]# g++ test.cpp -O2 -o O2_test 优化: [root@localhost LinuxC++]# g++ test.cpp -o no_O2_test -rwxr-xr-x 1 root root 9024 10月 5 08:58 no_O2_test -rwxr-xr-x 1 root root 8888 10月 5 08:57 O2_test
2.执行速度
未优化 [root@localhost LinuxC++]# time ./no_O2_test real 0m1.067s user 0m0.005s sys 0m0.004s 优化: [root@localhost LinuxC++]# time ./O2_test real 0m0.071s user 0m0.002s sys 0m0.005s
通过上述可知,优化后的可执行文件明显比未做优化的文件小,执行速度上优化后的速度(0.071s)未优化的速度(1.067s)有明显的提升。
3.-l和-L 指定库文件 | 指定库文件路径
-l参数(小写)就是用来指定程序要链接的库,-l参数紧接着就是库名,在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接 链接glog库 [root@localhost LinuxC++]#g++ -lglog test.cpp 如果库文件没放在上面三个目录里,需要使用-L参数(大写)指定库文件所在的目录 -L参数跟着的是库文件所在的目录名 #链接mytest库,libmytest.so在/home/目录下 [root@localhost LinuxC++]#g++ -L/home/LinuxC++ -lmytest test.cpp
4.-I(大写i) 指定头文件搜索目录
-I
/usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件在/home/LinuxC++目录里,那编译命令就要加上-I/home/LinuxC++ 参数了,如果不加你会得到一个xxxx.h: No such file or directory的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.指定。上面我们提到的-cfalgs参数就是用来生成-I参数的。
5.-Wall 打印告警信息
打印出gcc提供的告警信息 [root@localhost LinuxC++]#g++ -Wall test.cpp
6.-w 关闭告警信息
[root@localhost LinuxC++]#g++ -w test.cpp
7.-std=c++11 设置编译标准,不设置有可能默认其他的标准如C++98等
使用c++11标准编译标准 [root@localhost LinuxC++]#g++ -std=c++ test.cpp
-
-o指定文件名
指定即将产生的文件名 指定输出可执行文件名test,如果不写的默认a.out 不指定: [root@localhost LinuxC++]# g++ test.cpp -rwxr-xr-x 1 root root 8.9K 10月 5 10:52 a.out 指定: [root@localhost LinuxC++]# g++ test.cpp -o no_test -rwxr-xr-x 1 root root 8.9K 10月 5 10:54 no_test -rw-r--r-- 1 root root 300 10月 5 10:04 test.cpp
9.-D宏定义
在使用gcc/g++编译的时候定义宏 常用场景: -DDEBUG 定义DEBUG宏,可能文件中有DEBUG部分的相关信息,用DDEBUG来选择开启或者关DEBUG
示例代码:
// -Dname 定义宏name,默认定义内容为字符串“1” #include <stdio.h> int main() { #ifdef DEBUG printf("DEBUG LOG\n"); #endif printf("in\n"); } // 1. 在编译的时候,使用gcc -DDEBUG main.cpp // 2. 第七行代码可以被执行
注:使用 man gcc
命令可以查看gcc英文使用手册
3.3【实战】g++命令编程
安装tree目录结构查看指令3 directories, 4 files
[root@localhost LinuxC++]#yum -y install tree [root@localhost LinuxC++]# tree . . ├── bin │ └── O2_test ├── include │ └── Swap.h └── src ├── Swap.cpp └── test.cpp
3.3.1直接编译
最简单的直接编译,并运行
# 将 main.cpp src/Swap.cpp 编译为可执行文件 [root@localhost LinuxC++]#g++ main.cpp src/Swap.cpp -Iinclude # 运行a.out ./a.out
增加参数编译,并运行
# 将 main.cpp src/Swap.cpp 编译为可执行文件 附带一堆参数 [root@localhost LinuxC++]#g++ main.cpp src/Swap.cpp -Iinclude -std=c++11 -O2 -Wall -o b.out # 运行 b.out [root@localhost LinuxC++]#./b.out
3.3.2 生成库文件并编译
链接静态库生成可执行文件1:关键字ar rs .a
## 进入src目录下 $cd src # 汇编,生成Swap.o文件 [root@localhost LinuxC++]#g++ Swap.cpp -c -I../include # 生成静态库libSwap.a [root@localhost LinuxC++]#ar rs libSwap.a Swap.o ## 回到上级目录 [root@localhost LinuxC++]#cd .. # 链接,生成可执行文件:staticmain [root@localhost LinuxC++]#g++ main.cpp -Iinclude -Lsrc -lSwap -o staticmain
链接动态库生成可执行文件2:关键字: -fPIC -shared .so
-shared 参数时,目的是使源码编译成动态库 .so 文件
-fPIC的作用是 告知编译器 生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库。
## 进入src目录下 [root@localhost LinuxC++]#cd src # 生成动态库libSwap.so [root@localhost LinuxC++]#g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so ## 上面命令等价于以下两条命令 #[root@localhost LinuxC++]# gcc Swap.cpp -I../include -c -fPIC #[root@localhost LinuxC++]#gcc -shared -o libSwap.so Swap.o ## 回到上级目录 [root@localhost LinuxC++]#cd .. # 链接,生成可执行文件:sharemain [root@localhost LinuxC++]#g++ main.cpp -Iinclude -Lsrc -lSwap -o sharemain
3.3.3 运行可执行文件,两种方式
动态库和动态库最大区别,在编译过程中。静态库文件直接被编译到可执行文件中,意味着运行可执行文件不需要执行静态库的路径等;相反,动态库需要指定库文件的路径。下面的是两种运行的方式:
静态库方式1:
#直接运行 [root@localhost LinuxC++]#./staticmain
动态库方式2:
# 运行可执行文件 [root@localhost LinuxC++]#LD_LIBRARY_PATH=src ./sharemain
第四讲 GDB调试器
前言
-
GDB(GUN Debugger)是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发C/C++最常用的调试器;
-
程序员可以使用GDB来跟踪程序中的错误,从而减少程序员的工作量
-
Linux开发C/C++一定要熟悉GDB
-
VSCode是通过调用调试器来实现C/C++ 的调试工作的;
延伸:
-
Windows 系统中,常见的集成开发环境(IDE),如 VS、VC等,它们内部已经嵌套了相应的调试器
GDB主要功能:
-
设置断点(断点可以是条件表达式)
-
使程序员指定的代码上暂停执行,便于观察
-
单步执行程序,便于调试
-
查看程序中变量值的变化
-
分析奔溃程序产生的core文件
4.1常用调试命令参数
调试开始:执行gdb[exefilenme],进入gdb调试程序,其中exefilename为要调试的可执行文件名
以下命令后括号为命令的简化使用,比如run(r),直接命令r就代表命令run $(gdb)help(h) #查看命令帮助,具体命令查询在gdb中输入help+命令 $(gdb) run(r) #重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件) $(gdb) start 单步执行 运行程序,停在第一条执行语句 $(gdb) list(l) 查看源代码(list-n,从第n行开始查看代码,list+函数名:查看具体函数) $(gdb) set 设置变量的值 $(gdb) next(n)单步调试(逐过程。函数直接执行) $(gdb) step (s) 单步调试(逐语句:跳入自定义函数内部执行) $(gdb) backtrace(bt)查看函数的调用的栈帧和层级关系 $(gdb) frame(f)切换函数的栈帧 $(gdb) info(i) 查看函数内部局部变量的数值 $(gdb) continue(c)继续执行 $(gdb) print(p)打印值及地址 $(gdb) quit(q)退出gdb $(gdb) break+num(b)在第num行设置的断点 $(gdb) dispaly 追踪查看具体变量值 $(gdb) undispaly 取消追踪观察变量 $(gdb) watch 被设置观察点的变量发生修改时,打印显示 $(gdb) i watch 显示观察点 $(gdb) enable breakpints 启动断点 $(gdb) disable breakpoints 禁用断点 $(gdb) x 查看内存x/20xw显示20个单元,16进制,4字节每单元 $(gdb)run argv[1] argv[2]调试时命令传参 $(gdb)set follow-fork-mode child Makefile项目管理:选择跟踪父子进程(foerk())
tips:
1.编译程序时需要加上-g,之后才能用gdb进行调试:gcc -g mian.cpp -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 = sum + i; i = i + 1; } cout << "sum = " << sum << endl; cout << "The program is over." << endl; return 0; }
打断点调试过程中遇到:Missing separate debuginfos, use:debuginfo-install glibc-2.17-322.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64,因为一般发行版内核中是不带debuginfo,需要自行去安装,找到与内核匹配的:Index of /7/x86_64
第五讲 IDE-VSCode
在线安装:在linux系统(centOS7)中安装VSCode(Visual Studio Code)_沈醉不知的博客-CSDN博客
5.1界面介绍
5.2插件安装
5.3快捷键
5.4【实战】2个小项目
第六讲 CMake
前言:
CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装(编译过程)
CMake可以说已经成为大部分C++开源项目标配
6.1cross-platform development
跨平台开发环境
6.2语法特性介绍
-
基本语法格式 :指令(参数1 参数2...)
-
参数使用括弧括起
-
参数之间使用空格或分号分开
-
指令是大小写无关的,参数和变量是带小写相关的
-
set(HELLO hello.cpp)
-
add_executable(hello main.cpp hello.cpp)
-
ADD_EXECUTABLE(hello.cpp main.cpp $
-
{HELLO})变量使用${}方式取值,但是在IF控制语句中是直接使用变量名
6.3重要指令和CMake常用变量
6.3.1重要指令
-
cmake_minimum_required 指定CMake的最小版本要求
#CMake最小版本要求为2.8.3
cmake_minimum_required(VERSION 2.8.3)
语法:cmake_minimum_required(VSERION versoinNumber[FATAL_ERROR])
-
project -定义工程名称,并可指定工程支持的语言
#指定工程名称为HELLOWORLD
project(HELLOWORLD)
语法:project(projectname CXX[Java])//[]内可省略
-
set-显示的定义变量
定义SRC变量,其值为main.cpp hello.cpp
set (SRC main.cpp hello.cpp)
-
include_directories-向工程中添加多个特定的头文件所搜路径-->相当于指定g++编译器的-I(i的小写)参数
#将/usr/include/myinclude和./include添加到头文件搜索路径
include_directories(/usr/include/myinclude ./include)
语法:include_directories(AFTER|BEFORE dir1 dir2 …)
-
link_directories--向工程中添加多个特定的库文件搜索路径--相当于指定g++编译器的-L参数
#将/usr/lib/mylib和./lib添加到库文件搜索路径
link_directories(/usr/lib/mylib ./lib)
语法:link_directories(dir1 dir2 …)
-
add_library--生成库文件
#通过变量SRC生成 libhello.so共享库
add_library(hello SHARED ${SRC}
语法:add_library(libname SHARED|STATIC|MODULE source1 source2 … sourceN)shared:动态库关键字;static:静态库关键字
-
add_compile_options-添加编译参数
#添加编译参数-Wall -std=c++11
add_compile_options(-Wall -std=c++11 --O2)
语法:**add_compile_options(<options>...)
-
add_executable--生成可执行文件
#编译main.cpp生成可执行文件main
add_executable(main main.cpp)相当于g++ main.cpp -o main
语法:add_executable(exename source1 source2 … sourceN)
-
target_link_libraries--为target添加需要连接的共享库-->相当于指定g++编译器-l(L的小写)参数
将hello动态库文件连接到库执行文件main
target_link_libraries(main hello)
语法:target_link_libraries(target library1library2…)
-
add_subdirectory -向当前工程添加存放源文件的子目录,并可以指定中间二进制和目录二进制存放的位置
#添加src子目录,src中需要有一个CMakeLists.txt
add_subdirectory(src)
语法:add_subdirectory(source_dir binary_dir)
-
aux_source_directory --发现一个目标下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
#定义SRC变量,其值为当前目录下所有的源代码文件
aux_source_directory(. SRC)
#编译SRC变量所代表的源代码文件,生成main可执行文件
add_executable(main ${SRC})
语法:aux_source_directory(dir VARIABLE)
6.3.2 CMake 常用变量
CMAKE_C_FLAGS gcc编译选项
CMAKE_CXX_FALGS g++ 编译选项
在CMAKE_CXX_FALGS编译选项后追加 -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
<projectname>__BINARY_DIR
1.这三个变量指代的内容是一致的。
2.如果in source build指的是工程顶层目录
3.如果out-of-source编译,指的是工程编译发生的目录
4.PROJECT_BINARY_DIR跟其他指令稍有区别,不过现在可以理解为他们是一致的
-
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>__SOURCE_DIR
1.这三个变量指代的内容是一致的,不论采用何种编译方式都是工程顶层目录
2.也就是在in source build 时,它跟CMAKE_BINARY_DIR等变量一致
3.PROJECT_SOURCE_DIR跟其他指令稍有区别,可以理解为他们是一致的
CMAKE_C_COMPILER:指定C编译器
CMAKE_CXX_COMPILER:指定C++编译器
EXECUTABLE_OUTPUT_PATH:可执行文件输出的存放路径
LIBRARY_OUTPUT_PATH:库文件输出的存档路径
6.4CMake编译工程
CMake目录结构:项目主目录存在一个CMakeLists.txt文件
两种方式设置编译原则:
1.包含源文件的子文件包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加目录即可;
2.包含源文件的子文件加未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中;
6.4.1 编译流程
在Linux平台下使用CMake构建从C/C++工程的流程如下:
-
手动编写CMakeLists.txt
-
执行领命cmake PATH生成Makefile(PATH 是顶层CMakeLists.txt所在的目录)
-
执行命令make进行编译
#important tips . # 表示当前目录 ./ #表示当前目录 .. #表示上级目录 ../ #表示上级目录
6.4.2 两种构建方式
内部构建(a in-source build):不推荐使用
内部构建会在同级目录下产生一大堆中间文件,这些中间文件不是我们最终所需要的的,和工程源文件放在一起会显得杂乱无章
## 内部构建 # 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件 [root@localhost LinuxC++]#cmake . # 执行make命令,生成target [root@localhost LinuxC++]#make
外部构建(out-source build)=推荐使用=
将编译输出文件与源文件放到不同目录中 #1.在当前目录下,创建build文件夹 [root@localhost LinuxC++]#mkdir build #2.进入到build文件夹 [root@localhost LinuxC++]#cd build #3.编译上级目录的CMakeLists.txt,生成Makefile和其他文件 [root@localhost LinuxC++]#cmake .. #4.执行make命令,生成target [root@localhost LinuxC++]#make
6.5【实战】CMake代码实战
6.5.1最小CMake工程
# #set the minimum version of CMake that can be used cmake_minimum_required(VERSON 3.0) #set the project name project (HELLO) #Add an executable add_executable(hello_cmake main.cpp)
6.5.2 多目录工程-直接编译
#set the minimum version of CMake that can be used cmake_minimum_required (VERSION 3.0 ) #project name project (SWAP) #head file pat include_directory (src DIR_SRCS) #source directory files to var add_executable(swap_02 ${TEST_MATH}) #add link library target_link_libraries(${FS_BUILD_BINARY_PREFIX}sqrt ${LIBRARIES})
6.5.3 多目录工程-生成库编译
#set the minimum vresion of CMake that can be used cmake_minimum_required(VERSION 3.0) #project name project(SWAP_LIBRARY) #add complie options add_compile_options("-Wall -std=c++11") #set CMAKE_BUILD_TYPE set( CMAKE_BUILD_TYPE Debug) #set ouput binrary path set (EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) #################################################### #Create a library #################################################### #generate the static library from the library sources add_library( swap_library STATIC src/Swap.cpp) target_include_directories (swap_lib PUBLIC ${PROJECT_SOURCE_DIR}/include) ################################################# #create an executable ################################################# #add an executable with the above sources add_executable (swap_01 main.cpp) #link the new swap_01 target with the swap_lib target target_link_libraries( swap_01 swap_liby)
第七讲【实战】:使用VSCode进行完整项目开发
7.1 合理设置项目目录
7.2 编写项目源文件
7.3 编写CMakeLists.txt构建项目编译规则
7.4 使用外部构建,手动编译CMake项目
7.5 配置VSCode的json文件并调试项目