文章目录
1 Linux的开发环境搭建
1.1 编译器,调试器安装
安装GCC和GDB
sudo apt install build-essential gdb
1.2 CMake安装
sudo apt install cmake
2 GCC编译器
GCC 编译器支持编译 Go、Objective-C,Objective-C ++,Fortran,Ada,D 和 BRIG(HSAIL)等程序;
VSCode其实也是通过调用GCC编译器来实现C/C++的编译工作的:
- 使用 gcc 指令编译 C 代码
- 使用 g++指令编译 C++ 代码
2.1 编译过程
总的来说,其编译的指令为:
g++ 源文件 -o 生产的文件名
上面的-o
编译选项来为将产生的可执行文件用指定的文件名
【详细过程】
- 预处理
由.cpp
文件转化成.i
文件。
可以通过g++的-E
选项实现:仅对输入文件进行预处理(例如:头文件的展开、注释的删除、宏替换) - 编译
由.i
文件转化为.s
文件。
可以通过g++的-S
选项实现:使cpp代码产生了汇编语言文件后停止编译 - 汇编
由.s
文件转化为.o
文件。
可以通过g++的-C
选项实现:使原代码翻译为机器语言的代码文件 - 链接
由.o
文件生成可执行文件。
【简便过程】
以test.cpp为例:
g++ test.cpp -o test
2.2 g++常用选项
除了上面提到的三个选项,还有一个选项-o
,还存在:
编译带调试信息的可执行文件
-g
编译带调试信息的可执行文件。
优化源代码
-o[n]
优化源代码。
所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。如:gcc -O2 test.cpp -o test
。其中:
-O
同时减小代码的长度和执行时间,其效果等价于-O1
;
-O0
表示不做优化;
-O1
为默认优化;
-O2
除了完成-O1
的优化之外,还进行一些额外的调整工作,如指令调整等(常用);
-O3
则包括循环展开和其他一些与处理特性相关的优化工作;
这些选项将使编译的速度比使用 -O0 时慢,但通常产生的代码执行速度会更快。好处不仅仅是运行更快,还会防止反编译你的代码看清你代码的逻辑。
指定库文件
-l
和 -L
指定库文件或者指定库文件路径
首先:-l参数(小写)就是用来指定程序要链接的库,-l参数紧接着就是库名
在/lib
和/usr/lib
和/usr/local/lib
里的库直接用-l参数就能链接,如:
# 链接glog库
g++ -l glog test.cpp
如果库文件没放在上面三个目录里,需要使用-L参数(大写)指定库文件所在目录,-L参数紧跟着的是库文件所在的目录名,再通过小写-l指定要连接的库如:
# 链接mytest库,libmytest.so在/home/bing/mytestlibfolder目录下
g++ -L /home/bing/mytestlibfolder -l mytest test.cpp
指定头文件
-I
指定头文件搜索目录。
/usr/include
目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上I/myinclude参数了。
打印警告信息
-Wall
打印警告信息。
-w
关闭警告信息。
设置编译标准
-std=c++11
使用c++11标准编译test.cpp。
指定输出文件名
-o
指定输出文件名。上面2.1有详细解释!
定义宏
-D
定义宏。在使用gcc/g++
编译的时候定义宏:
常用场景:-DDEBUG
定义DEBUG宏,可能文件中有DEBUG宏部分的相关信息,用个DDEBUG来选择开启或关闭DEBUG
-Dname
定义宏name,默认定义内容为字符串“1”
动态库常用的几个选项
-fpic
生成与位置无关的代码
-shared
生成共享目标文件,通常用在建立共享库(动态库)时
2.3 查看执行程序时间
其实就是用linux的time指令:time+可执行程序的名字。如:
time ./test.cpp
2.4 生成库文件并编译的实战例子
首先先看例子用到的文件:
其中main.cpp是执行的主程序,里面用到了swap.h的头文件,而头文件里面的函数在/src里面的swap.cpp。
直接编译
g++ main.cpp src/Swap.cpp -Iinclude
此时会生成默认名字的a.out
。
注意需要-I去制定好头文件!
链接静态库生成可执行文件
【静态库的定义】
在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,该方式对应的链接方式称为静态链接。
【静态库的特点】
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
【静态库的制作】
- g++获得.o文件
- 将.o文件打包,使用ar工具(archive)生成静态库libxxx.a。(windows下无论静态动态库的后缀都是.dll)
【静态库的实现方式】
## 进入src目录下
cd src
# 汇编,生成Swap.o文件
g++ Swap.cpp -c -I../include
# 生成静态库libSwap.a
# 注意这个.a的静态库文件必须要有lib前缀,并且lib后面的才是符号名
# 链接的时候,使用lib后面的符号名
# 注意后面的.o文件可以是多个,共同打包成.a静态库
# 此处的r选项为将文件插入备存文件中
# 此处的s选项为索引(多个.o文件的索引)
ar rs libSwap.a Swap.o
## 回到上级目录
cd ..
# 链接,生成可执行文件:staticmain
g++ main.cpp -Iinclude -Lsrc -lSwap -o staticmain
【静态库的文件执行】
直接运行可执行文件即可:./staticmain
链接动态库生成可执行文件
【动态库的定义】
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。
【动态库的特点】
- 动态库把对一些库函数的链接载入推迟到程序运行的时期。
- 可以实现进程之间的资源共享。(因此动态库也称为共享库)
- 升级时,用户只需要更新动态库即可,增量更新。
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
【动态库的实现】
## 进入src目录下
cd src
# 生成动态库libSwap.so
g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so
## 上面命令等价于以下两条命令(类似上面静态库的两条)
# gcc Swap.cpp -I../include -c -fPIC
# gcc -shared -o libSwap.so Swap.o
## 回到上级目录
cd ..
# 链接,生成可执行文件:sharemain
g++ main.cpp -Iinclude -Lsrc -lSwap -o sharemain
【链接动态库文件的执行】
相比静态库文件的执行,它需要多一步去指定动态文件库:
LD_LIBRARY_PATH=src ./sharemain
(一句中前面指定动态库路径,后面执行)
3 GDB调试器
GDB(GNU Debugger)是一个用来调试C/C++程序的调试器。
VSCode是通过调用GDB调试器来实现C/C++的调试工作的;
【前置】编译程序时需要加上-g
,之后才能用gdb进行调试:gcc -g main.c -o main
3.1 GDB调试命令
【调试开始】执行gdb [exefilename]
,进入gdb调试程序,其中exefilename为要调试的可执行文件名。
【tips】
- 在GDB调试时,回车键为重复执行上一个命令
- 以下命令后括号内为命令的简化使用,比如
run(r)
,直接输入命令 r 就代表命令run。
常用指令 | 说明 |
---|---|
help(h) | 查看命令帮助,具体命令查询在gdb中输入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 |
3.2 GDB打断点
常用指令 | 说明 |
---|---|
break+num(b) | 在第num行设置断点 |
info breakpoints | 查看当前设置的所有断点 |
delete breakpoints num(d) | 删除第num个断点 |
display | 追踪查看具体变量值 |
undisplay | 取消追踪观察变量 |
watch | 被设置观察点的变量发生修改时,打印显示 |
i watch | 显示观察点 |
enable breakpoints | 启用断点 |
disable breakpoints | 禁用断点 |
4 VSCode高频快捷键
功能 | 快捷键 |
---|---|
变量统一命名(改一个,自动改全部) | F2 |
转到定义处 | F12 |
将当前行进行上下移动 | alt + up/dowm |
代码格式化 | Shift+Alt+F 或者(右键就有选项) |
跳转到下一个 Error 或 Warning当有多个错误时可以逐个跳转 | F8 |
功能 | 快捷键 |
---|---|
关闭当前文件 | ctrl + w |
打开命令面板 | ctrl +shift+ p |
打开/关闭终端 | ctrl+ ` |
打开/关闭侧边栏 | ctrl + B |
5 Makefile
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,而Makefile文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile 文件就像一个shell脚本一样, 也可以执行操作系统的命令。
5.1 Makefile规则
一个Makefile文件中可以有一个或者多个规则
# 一条规则由一个目标、一个依赖和其命令组成
XXX(目标):xxx(依赖)
xxx(shell命令)
...
- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令前必须要有
TAB
来缩进)
5.2 Makefile工作原理
0、注意Makefile在执行时,如果只打make
那么默认执行第一个规则,也可以make XXX(目标名)
来指定执行某个规则。
1、命令在执行之前,需要先检查规则中的依赖是否存在。
- 存在就执行命令
- 如果不存在,就向下检查其他的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了就执行该规则的命令。
2、检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间(配合好这个原理,可以修改一个文件,而不用重新编译)
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行。
5.3 变量
自定义变量
可以直接在文件中书写变量定义!
规则:变量名=变量值
代码实例:var=hello
预定义变量
AR
:归档维护程序的名称,默认值伪ar
CC
:C编译器的名称,默认值伪cc
CXX
:C++编译器的名称,默认值伪g++
$@
:目标的完整名称
$<
:第一个依赖文件的名称
$^
:所有的依赖文件
获取变量的值
$(变量名)
【例子】
app:main.c a.c b.c
gcc -c main.c a.c b.c
可以用下面的方式来表示:
# 自动变量只能在规则的命令中使用
app:main.c a.c b.c
$(CC) -c $^ -o $@
5.4 模式匹配
可以通过通配符%
来匹配字符串。例如:
src=sub.o add.o div.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
# 上面这句需要依赖下面那么多.o文件
add.o : add.c
gcc -c add.c
div.o : div.c
gcc -c div.c
sub.o: sub.c
gcc -c sub.c
我们便可以通过下面一句话来省略上面一个个的敲目标、依赖和命令
%.o:%.c
$(CC) -c $< -o $@
5.5 函数
返回指定目录下指定类型的文件列表
【声明格式】
$(wildcard XXX(参数)#...)
- 功能:获取指定目录下指定类型的文件列表
- 参数:XXX参数指的是某个或者多个目录下的对应的某种类型的文件,如果多个目录,一般使用空格间隔。
- 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
【示例】
$(wildcard *.c ./sub/*.c)
返回值:当前文件夹下面的所有.c后缀的文件名以及当前文件夹下sub子文件夹下的所有.c后缀的文件名。
替换指定格式的字符串
该函数常常配合上面的函数一起使用
【声明格式】
$(patsubst <pattern>,<replacement> ,<text>)
- 功能 : 查找
<text>
中的单词(单词以"空格"、“Tab"或“回车""换行"分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换。 <pattern>
可以包括通配符%
,表示任意长度的字串。- 返回 : 函数返回被替换过后的字符串
【案例】
$(patsubst %.c, %.o, x.c bar.c)
返回值格式:x.o bar.o
5.6 一些tips规则
如果在编译的时候生成了很多.c
或者.o
文件,是可以再额外加一个规则去删掉的。但是如果直接下面那样clean:
如果遇到了clean名,就会触发工作原理的第二条,而导致不执行,因此我们需要加一个.PHONY
来声明它是一个伪目标。
.PHONY:clean
clean:
#此处的objs是上面替换下来的格式文件,也就是中间文件
rm $(objs) -f
随后在终端中,执行make clean
即可。
6 CMake
CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装(编译过程)。
6.1 语法特性介绍
【基本语法格式】指令(参数1 参数2...)
- 参数需要用括弧括起来
- 参数之间使用空格或分号分开
- 指令是大小写无关的,参数和变量是大小写相关的
- 变量使用
${}
方式取值,但是在 IF 控制语句中是直接使用变量名
例如:
set(HELLO hello.cpp)
add_executable(hello main.cpp ${HELLO})
6.2 重要指令
指定CMake的最小版本要求
语法:cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
【常用案例】cmake_minimum_required(VERSION 2.8.3) # CMake最小版本要求为2.8.3
定义工程名称,并可指定工程支持的语言
语法: project(projectname [CXX] [C] [Java])
【常用案例】project(HELLOWORLD) # 指定工程名为HELLOWORLD
显式的定义变量
语法:set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
【常用案例】set(SRC sayhello.cpp hello.cpp) # 定义SRC变量,其值为sayhello.cpp hello.cpp
生成库文件语法
add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
【常用案例】add_library(hello SHARED ${SRC}) # 通过变量 SRC 生成 libhello.so 共享库(也就是上面提到的动态库)
向工程添加多个特定的头文件搜索路径
这个相当于g++编译器的-I
参数
语法: include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
【常用案例】include_directories(/usr/include/myincludefolder ./include) # 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
向工程添加多个特定的库文件搜索路径
这个相当于指定g++编译器的-L
参数
语法:link_directories(dir1 dir2 ...)
【常用案例】link_directories(/usr/lib/mylibfolder ./lib) #将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
为 target 添加需要链接的共享库
相同于指定g++编译器-l
参数
语法:target_link_libraries(target library1<debug | optimized> library2...)
【常用案例】target_link_libraries(main hello) # 将hello动态库文件链接到可执行文件main
添加编译参数
语法:add_compile_options( <option>...)
这里的option主要参考g++那边的选项!
【常用案例】add_compile_options(-Wall -std=c++11 -O2) # 添加编译参数 -Wall -std=c++11 -O2
生成可执行文件
语法:add_executable(exename source1 source2 ... sourceN)
【常用案例】add_executable(main main.cpp) # 编译main.cpp生成可执行文件main
向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
【常用案例】add_subdirectory(src) # 添加src子目录,src中需有一个CMakeLists.txt(无论有无内容,必须要有)
发现一个目录下所有的源代码文件并将列表存储在一个变量中
这个指令临时被用来自动构建源文件列表。
语法: aux_source_directory(dir VARIABLE)
【常用案例】
# 定义SRC变量,其值为当前目录下所有的源代码文件
aux_source_directory(. SRC)
# 编译SRC变量所代表的源代码文件,生成main可执行文件
add_executable(main ${SRC})
6.3 CMake常用变量
编译选项
CMAKE_C_FLAGS
gcc编译选项
CMAKE_CXX_FLAGS
g++编译选项
# 在CMAKE_CXX_FLAGS编译选项后·追加· -std=c++11 -Wall显示警告信息
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
编译类型
CMAKE_BUILD_TYPE 编译类型(Debug调试, Release发布)
Debug自动加-g参数,Release会加上o3优化以及关闭debug调试
# 设定编译类型为debug,调试时需要选择debug
set(CMAKE_BUILD_TYPE Debug)
# 设定编译类型为release,发布时需要选择release
set(CMAKE_BUILD_TYPE Release)
指定编译器
CMAKE_C_COMPILER
:指定C编译器
CMAKE_CXX_COMPILER
:指定C++编译器
指定源代码目录
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
_BINARY_DIR
这三个变量指代的内容是一致的:
- 如果是 in source build,指的就是工程顶层目录。
- 如果是 out-of-source 编译,指的是工程编译发生的目录
指定工程顶层目录
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
_SOURCE_DIR
其实这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。因此在 in source build时,他跟 CMAKE_BINARY_DIR 等 变量一致。
【用途】该变量会自动赋值CMakeLists.txt所在的路径,因此常用该变量来指示路径。
指定输出目录
EXECUTABLE_OUTPUT_PATH
:可执行文件输出的存放路径
LIBRARY_OUTPUT_PATH
:库文件输出的存放路径
6.4 CMake编译工程
编译规则
CMake目录结构:项目主目录存在一个CMakeLists.txt文件.
【两种方式设置编译规则】
- 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过
add_subdirectory
添加子目录即可; - 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的
CMakeLists.txt中;
编译流程
- 手动编写 CMakeLists.txt。
- 执行命令行的命令
cmake PATH
生成 Makefile ( PATH 是顶层CMakeLists.txt 所在的目录 )。 - 执行命令行的命令
make
进行编译。(而若文件之间的关系不变的话,修改源码,只需要make
即可,并且cmake只会编译修改的源文件,十分节省时间!)
构建方式
一共有两种构建的方式:
1、内部构建(in-source build):不推荐使用
内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。
## 内部构建
# 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .
# 执行make命令,生成target
make
2、外部构建(out-of-source build):推荐使用
将编译输出文件与源文件放到不同目录中
## 外部构建
# 1. 在当前目录下,创建build文件夹
mkdir build
# 2. 进入到build文件夹
cd build
# 3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件
cmake ..
# 4. 执行make命令,生成target
make
7 配置VSCode的json文件
7.1 json文件介绍
多个文件时,通过配置VSCode中的task.json
和launch.json
(位于项目的.vscode文件夹下),可以通过点击运行,来自动化运行最后生成的可执行文件或者调试该可执行文件。(单个文件直接使用VSCode中的g++运行即可)
1、launch.json
– for debug
作用:配置调试信息,用来调试编译好的文件(主要用到的参数有两种):
program
:可执行文件的路径;
preLaunchTask
:执行调试前所执行的task
2、tasks.json
– for build before debug
作用:包含调试前的操作指令,用来做调试前的编译工作:编译代码,并生成可执行文件。
可以避免每次修改代码后,手动编译;即tasks.json其实是和手动编译 的作用等价的。
【注意】label 应与launch.json中的preLaunchTask
名字一致
7.2 launch.json的模板
{
"version": "0.2.0",
"configurations": [
{
"name": "g++ - 生成和调试活动文件", //配置名称,显示在启动配置下拉菜单中
"type": "cppdbg", //配置类型
"request": "launch", //请求配置类型
"program": "${workspaceFolder}/build/main", //代表的可执行文件的绝对路径
"args": [], //传递给程序的命令行参数
"stopAtEntry": false, //可选参数。如果为true,则调试程序应在目标的入口点处停止。
"cwd": "${workspaceFolder}", //cd到工程的顶层目录
"environment": [], //要添加到程序中的环境变量{"name":"config","value":"Debug"}
"externalConsole": false, //true:启动控制台;false:在vscode的集成的控制台显示
"MIMode": "gdb", //调试方式
//
"setupCommands": [
{
"description": "为 gdb 启用整齐打印", //
"text": "-enable-pretty-printing", //
"ignoreFailures": true //
}
],
"preLaunchTask": "Build", //调试前作的一个task,联合tasks.json进行自动化编译后调试
"miDebuggerPath": "/usr/bin/gdb" //调试程序的路径,要知道自己把调试程序安装到哪里了
}
]
}
7.3 tasks.json的使用CMake.txt的模板
下面的代码模板对应Linux下的外部构建的方式。windows下面自行修改
{
"version": "2.0.0",
"options": {
//外部构造中的可执行文件在build下
"cwd": "${workspaceFolder}/build"
},
"tasks": [
{
//对应编译流程第二步
"type": "shell",//定义任务是被认为是进程还是在shell中运行
"label": "cmake",
"command": "cmake",//task命令
//调用命令时传递的参数
"args": [
".."
]
},
{
//对应编译流程第三步
"label": "make",
"group": {
"kind": "build",
"isDefault": true
},
"command": "mingw32-make.exe",
"args": [
]
},
{
"label": "Build",//注意这里与launch.json的preLaunchTask参数需保持一致
"dependsOrder":"sequence",//按顺序执行
"dependsOn":[
"cmake",
"make"
]
}
],
}
7.4 C/C++ Project Generator
这是VSCode的一个插件,通过以下三步使用
- 打开命令面板(
ctrl + shift + P
) - 选择"Create c++ project"选项
- 选择项目所在目录
此时可以发现,工程文件和Makefile文件建立好了,task.json和launch.json也对应好了,有需求可以直接修改。
【细节】其默认执行的源码在src文件目录里的main.cpp,且其默认使用Makefile,而不是CMake,需要自己写。
8 运行中的错误代码查看
若在运行过程中报错,如何查看错误代码:
- 首先在
bash
中使用命令ulimit -a
,查看core file size
的大小是多少(默认为0) - 接下来,将core设为无限大,
ulimit -c unlimited
- 然后重新编译并运行,当再次报错时,会自动生成
core
文件,随后进入gdb xxx(可执行程序)
- 执行
core-file core
即可看到错误的原因以及代码行
9 引用
本文为学习笔记,其中大部分内容都是参考的UP主xiaobing1016的视频整理的,这里进行了摘要整理。在这里感谢UP主!
视频内容https://www.bilibili.com/video/BV1fy4y1b7TC
部分注释参考https://blog.csdn.net/weixin_50727642/article/details/124967813