Linux开发工具
# Linux 开发工具
课程列表_牛客网 (nowcoder.com) (opens new window)
# 环境搭建
安装Linux系统(虚拟机安装、云服务器) https://releases.ubuntu.com/bionic/
安装XSHELL、XFTP https://www.netsarang.com/zh/free-for-home-school/
- 安装Visual Studio Code https://code.visualstudio.com/
# 一、GCC
# 1、什么是GCC
- ◼ GCC 原名为 GNU C语言编译器(GNU C Compiler)
- ◼ GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言 译器。GNU 编译器套件包括 C、C++、Objective-C、Java、Ada 和 Go 语言前端,也包括了这些语言的库(如 libstdc++,libgcj等)
- ◼ GCC 不仅支持 C 的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行 选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数
-std=c99
启动 GCC 时,编译器支持 C99 标准。 - ◼ 安装命令
sudo apt install gcc g++
(版本 > 4.8.5) - ◼ 查看版本
gcc/g++ -v/--version
# 2、编程语言的发展
# 3、GCC的工作流程
# 4、gcc 和 g++ 的区别
gcc 和 g++都是GNU(组织)的一个编译器。
误区一:gcc 只能编译 c 代码,g++ 只能编译 c++ 代码。两者都可以,请注意:
- 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序
- 后缀为 .cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
- 编译阶段,g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc 命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接,为了统 一起见,干脆编译/链接统统用 g++ 了,这就给人一种错觉,好像 cpp 程序只能用 g++ 似的
误区二:gcc 不会定义 __cplusplus 宏,而 g++ 会
- 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释
- 如上所述,如果后缀为 .c,并且采用 gcc 编译器,则该宏就是未定义的,否则, 就是已定义
误区三:编译只能用 gcc,链接只能用 g++
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用 gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
- gcc -lstdc++ (-l表示链接,std是标准的意思,所以这个语句就是按c++标准链接的意思)
- gcc 命令不能自动和C++程序使用的库联接,所以通常使用 g++ 来完成联接。 但在编译阶段,g++ 会自动调用 gcc,二者等价
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用 gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
# 5、GCC常用参数选项
-E 命令对应生成 预处理后源代码(一般以 .i 为后缀)
-S 命令对应生成 汇编代码(一般以 .s 为后缀)
-c 命令对应生成库代码(一般以 .o 为后缀),为二进制不能直接运行
-
上图,倒数第一个 gcc test.c -S 和gcc test.c 指令表明,就算跳过前面 -E ,-S,-O指令,直接执行后面的指令,gcc也会自动把前面的转换完成,如 执行gcc test.c -S 后,直接跳过了test.i 生成 test.c 生成了 test.s文件(其实不是跳过,只是内部执行了)
如果不使用 -o 给生成文件重命名,gcc会自动给文件命名,好像只有 gcc test.c 执行后,会自动生成 a.out 可执行程序(如果执行的是
gcc test.c -S
那默认生成的好像就是test.s
文件,其他指令生成的都是原名)以上代码的 gcc 改成 g++ 也能一样完成
-o 可以有两种写法
1. gcc test.c -o a.out 2. gcc -o test.c a.out
1
2-D (看下图,shell里面的语句就相当于在
test.c
里定义了DEBUG
)- -D的写法可以像下图一样连起来写,也可以不连
-Wall 作用看上图(这里的警告就是,未使用的变量a)
-On 优化解释:
//好比我们有这样一串代码
int a,b,c,d;
a=10;
b=a;
c=b;
b=d;
//那经过优化就会变成这样c
int a,b,c,d;
a=10;
b=10;
c=10;
d=10;
//这样可以防止一些反汇编工具,对我们的可执行文件进行逆向处理,生成我们的源代码(因为优化后代码逻辑没有被发现,好比这里并不是全都赋值10,而是b=a,c=b。。。)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-I
,-L
,-l
具体使用可以看下面静态库使用部分
# 二、静态库与动态库
# 1、什么是库
- 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用 者一些可以直接拿来用的变量、函数或类。
- 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
- 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加 载到内存中供程序调用。
- 库的好处:1.代码保密 2.方便部署和分发
# 2、静态库的制作
# 2.1 命名规则
- 命名规则:
- Linux : libxxx.a
- lib : 前缀(固定)
- xxx : 库的名字,自己起
- .a : 后缀(固定)
- Windows : libxxx.lib
# 2.2 制作
- 静态库的制作🛠
- gcc 获得 .o 文件 (gcc 的-c 指令获得库代码😎)
- 将 .o 文件打包,使用 ar 工具(archive)
ar rcs libxxx.a xxx.o xxx.o
- r – 将文件插入备存文件中 (插入 .o 文件)
- c – 建立备存文件 (我们用ar指令创建出来的文件就是备存文件,c:create创建)
- s – 索引 (因为 .o 文件不止一个会有很多,所以要有索引去查找)
tree
命令是将文件以树状显示
# 3、静态库的使用
head.c 和 和要生成.o文件的c文件并不在同一目录下,但是.c 文件却include了head.c
//sub.c #include <stdio.h> #include "head.h" int subtract(int a, int b) { return a-b; }
1
2
3
4
5
6
7
8
- gcc 编译使用
-I
指定 include 包含文件的搜索目录
- gcc 编译使用
- 制作静态库(注意命名规则)
- 因为此时我们制作的静态库在
src
目录下,但是我们想要把他放得到 lib目录下,所以使用mv
命令移动文件
- 因为此时我们制作的静态库在
head.c
和main.c
不在同一目录下,所以要指定head.c
的搜索目录,但是因为我们制作的库和mian也不在同一目录,所以必须指定库的位置和使用的库
-l
在程序编译时,指定使用的库(注意,我们的库名是 **suanshu
**而不是libsuanshu
也不要写libsuanshu.a
)
-L
指定编译时,搜索库的路径
# 4、动态库的制作
# 4.1 命名规则
- Linux : libxxx.so
- lib : 前缀(固定)
- xxx : 库的名字,自己起
- .so : 后缀(固定)
- 在Linux下是一个可执行文件
- Windows : libxxx.dll
# 4.2 制作
- gcc 得到 .o 文件,得到和位置无关的代码
gcc -c –fpic/-fPIC a.c b.c
- gcc 得到动态库
gcc -shared a.o b.o -o libcalc.so
- 上面命令里的
libcalc.so
不是固定的,而是libXXX.so ,XXX就是库名
# 4.3 制作流程
# 4.3.1 文件准备
- 创建目录文件
mkdir 目录名
- 拷贝文件,
cp calc library ../lesson06
- 将 当前目录下的 calc文件夹 和 library 文件夹拷贝到 上层目录的lessson06目录下
- 注意:这里这样拷贝没成功,下面有提示,这时要在 上面命令后面加上
-r
,表示以递归方式拷贝
# 4.3.2 制作动态库
先将拷贝过来的文件清理成我们需要的样子,如上图(就是把之前我们制作静态库时生成的
.o
和库文件删除),如果我们以来就没有这些东西,就不用啦。gcc -c –fpic add.c div.c mult.c sub.c
gcc 得到 .o 文件,得到和位置无关的代码gcc -shared add.o sub.o mult.o div.o -o libcalc.so
gcc 得到动态库 calc显示绿🐍的文件说明是可执行文件
# 4.3.3 使用动态库文件的问题
- 这里是已经把我们制作的
libcalc.so
文件 使用cp
命令copy到了 library 目录的lib目录下 - 黄色框的问题我们在制作静态库时有解释,就不再说了
- 在我们制作完动态库后,并使用其编译 main.c 的可执行文件时,我们可以正确生成可执行文件
main
,但是在运行可执行文件时就出现了蓝色框内的错误(加载共享库失败,找不到libcalc.so文件),这就与动态库的工作原理有关了
# 5、静态/动态库的工作原理
- 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
- 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
- 程序启动之后,动态库会被动态加载到内存中,通过
ldd
(list dynamic dependencies)命令检查动态库依赖关系 - 如何定位共享库文件呢?
- 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是 由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib 目录找到库文件后将其载入内存。
- 可以看出找不到
main
找不到我们的calc
库 - 倒数第三行 libc.so.6就标准的c库, =》后面的就是它的路径
- 倒数第二行那个就是系统的动态载入器(后面括号就是其在内存地址)
# 6、💛解决动态库使用的问题
方法1、配置环境变量,有3中配置方式
在配置系统级环境变量时,有时在配置完使用 ldd 指令检测时检测不到或者出现
ehco $LD_LIBRARY_PATH
后显示的路径为多条正确路径,可以关闭终端再重新链接就好了(反正就是确定配置好了,但是找不到依赖就出现打开终端,当然在终端配置方式除外,毕竟终端配置环境变量,一关闭终端就没了)
# 6.1 在终端中配置
缺点是这种配置方式是临时的,如果我们关闭终端,再次执行 main文件 ,就又找不到 calc 文件了
- 在终端中配置 LD_LIBRARY_PATH 环境变量
黄色框内命令解释:
export
设置或显示环境变量;$
表示获取,上面代码中就表示获取LD_LIBRARY_PATH
的值;:
因为环境变量里每个路径都是用:
隔开的,所以我们添加环境变量也要有:
隔开;- 这整句话就是获取原来
LD_LIBRARY_PATH
的值再添加上我们要加入的库文件的绝对路径,再赋给LD_LIBRARY_PATH
蓝色框命令解释:
echo
功能:显示文字;- 整句就是 获取
LD_LIBRARY_PATH
显示出来
从紫色(感觉像深粉)框:可以看到我们查找
main
依赖时可以找到库文件calc
了,因此执行 main文件成功
# 6.2 用户级别的配置
cd
回到主目录 ,vim .bashrc
,在文件末尾添加下图框内语句配置环境变量,然后保存退出
- 但这时我们的配置并没有生效,我们还需要执行
. .bashrc
或者source .bashrc
这两个命令使其立即生效(这两个命令作用一样,. 是简写)- source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令,通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。
# 6.3 系统级别的配置
- 在使用系统配置时,记得把之前终端配置(重启终端,感觉也可以按配置时的写法来删除)或者用户配置(
vim .bashrc
删除我们写进去的配置 )的配置删除
- 红色框是写错了文件,不用管😂,但是里面那个
~
表示的是在home目录(主目录里) - 系统级就是所以
vim /etc/profile
命令,在 /etc/profile 文件末尾添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件路径
,这个语句,然后保存并退出,使用source /etc/profile
使修改立即生效 - 注意:修改 /etc/profile 文件需要一定权限,所以要用
sudo
# 6.4 配置/etc/ld.so.cache文件列表
方法2:配置 /etc/ld.so.cache文件列表
- 因为 /etc/ld.so.cache 是二进制文件我们不能修改所以我们配置 /etc/ld.so.conf 文件
- 加入 /etc/ld.so.conf 文件 后,我们在里面写下我们要配置的库的绝对路径,这里我们就是写
/home/nowcoder/Linux/lesson06/library/lib
,保存退出
- 再使用
sudo ldconfig
命令更新配置
# 6.5 方式三
将我们的动态库文件放到/lib/,/usr/lib 这两个目录下,但是最好别这样做,因为这两个目录里本来就包含很多系统自带的文件,如果加到这里面,可能会导致和本来的文件重名,引起冲突
# 7、静态库和动态库对比
# 7.1 程序编译成可执行文件的过程
# 7.2 静态库制作过程
# 7.3 动态库制作过程
- 动态库加载不知道什么时候加载,也不知道加载到哪一块区域,所以要生成与位置无关代码
-fpic
# 7.4 静态库优缺点
# 7.5 动态库优缺点
# 三、Makfile
# 1、什么是Makefile
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中, Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编 译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就 像一个 Shell 脚本一样,也可以执行操作系统的命令。
- Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整 个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个 解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令, 比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。
# 2、Makefile 文件命名和规则
文件命名
- makefile 或者 Makefile
Makefile 规则
一个 Makefile 文件中可以有一个或者多个规则
目标 ...: 依赖 ... 命令(Shell 命令) ...
1
2
3目标:最终要生成的文件(伪目标除外)
依赖:生成目标所需要的文件或是目标
命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
Makefile 中的其它规则一般都是为第一条规则服务的。
make
命令执行 Makefile 文件
# 3、工作原理
- 命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的, 如果找到了,则执行该规则中的命令
- 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
- 将 Makefile 写成左边这样
- 然后使用
make
指令在线 Makefile 文件,因为我们缺乏 .o 的文件,所以就执行了下面的规则,去为我们第一个语句生成我们需要的依赖,然后再执行第一个规则
- 然后使用
- 执行完第一次
make
后再执行一次make
显示 “app” 已是最新,执行规则中的命令时,会比较目标和依赖文件的时间,我们刚执行完第一次make
后文件都没去改变,所以比较结果就是 目标文件的时间比依赖文件的时间晚,说明依赖文件没被修改过,如果已经有目标文件了,然后修改依赖文件,那依赖文件的时间就比目标文件晚了,说明被修改过
- 执行完第一次
- 4、我们修改
main.c
文件,然后再执行make
命令,只使用了生成main.o 的规则,和第一条规则,这就是有比较时间的好处(就是不用一次修改就全部重复执行)
# 4、变量
- 自定义变量 变量名=变量值 var=hello $(var)
- 预定义变量
- 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 $@
#@ 就相当于app ,$^ main.c a.c b.c ,$(CC) 就是 gcc
2
3
4
5
6
shell 里 #是注释的标识符
注意,命令前必须 tab缩进
上面的
$(CC) $(src) -o $(targrt)
也可以写作$(CC) $^ -o $@
1
# 5、匹配模式
add.o:add.c
gcc -c add.c
div.o:div.c
gcc -c div.c
sub.o:sub.c
gcc -c sub.c
mult.o:mult.c
gcc -c mult.c
main.o:main.c
gcc -c main.c
2
3
4
5
6
7
8
9
10
- 我们正常是这样写一旦文件较多就很麻烦,所以要所以通配符
%
%.o:%.c
gcc -c $< -o $@
2
- %: 通配符,匹配一个字符串
- 两个%匹配的是同一个字符串
- 一个规则中的两个或多个%,表示的是同一字符串
# 6、函数
# 6.1 获取文件函数
$(wildcard PATTERN...)
- 功能:获取指定目录下指定类型的文件列表
- 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
- 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
- 示例:
$(wildcard *.c ./sub/\*.c)
- 返回值格式: a.c b.c c.c d.c e.c f.c
- 就是获取当前目录下和当前目录下sub目录里所有 .c 文件
# 6.2 替换字符串函数
$(patsubst <pattern>,<replacement>,<text>)
- 功能:查找
<test>
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换。 <pattern>
可以包括通配符%
,表示任意长度的字串。如果<replacement>
中也包含%
,那么<replacement>
中的这个%
将是<pattern>
中的那个% 所代表的字串。(可以用\
来转义,以\%
来表示真实含义的%
字符)- 返回:函数返回被替换过后的字符串
- 示例:
$(patsubst %.c, %.o, x.c bar.c)
- 返回值格式: x.o bar.o
x.c
和bar.c
都和%.c
匹配 ,所以就将其替换为x.o bar.o
- 功能:查找
# 6.3 clean 和 PHONY
💙:我们注意到,我们每次执行完
make
都会有很多.o
文件,但是我们其实不需要这些.o
文件,所以有没有上面方法可以执行make
并且删除.o
文件呢?💛:我们在 Makefile 里添加黄色框部分内容
- 使用
make
执行,表示最新,因为我们make
第一个规则与clean
规则是无关的,第一条规则相关文件都没有发生改变,所以make
无效
- 使用
💚: 这时我们要用**
make clean
** 指定执行Makefile 里的clean
规则,可以看到 .o 文件都被删除了💜: 重新执行
make
,因为clean 与第一条规则无关,所以不会被执行🧡:
touch clean
我们自己创建一个**clean
** 文件 ,再make clean
,显示最新?- 原因是:我们的clean语句是不需要依赖的,但是我们有 clean文件,因为Makefile 检测更新的原理,
clean
文件始终比依赖晚创建(都没有依赖嘛),所以它更新,因此显示已最新
- 原因是:我们的clean语句是不需要依赖的,但是我们有 clean文件,因为Makefile 检测更新的原理,
❤:所以我们必须在 Makefile 里添加
.PHONY:clean
语句,表示clean
是一个伪目标,他就不会产生clean目标文件,就不会和我们文件里clean文件进行对比
# 四、GDB调试
- GDB的吉祥物,弓箭鱼🐟
# 1、什么是GDB
- GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。
- 一般来说,GDB 主要帮助你完成下面四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
# 2、准备工作
- 通常,在为调试而编译时,我们会关掉编译器的优化选项(
-O
), 并打开调试选项(-g
)。另外,-Wall
在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。 gcc -g -Wall program.c -o program
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。
- 因为打开了调试选项(
-g
),所以文件也会比普通文件更大
mv test.c test1.c
把test.c文件转移到了统一目录下的test1.c里(相当于重命名) ,所以在gdb test
时因为我们找不到test.c
文件了,就出现了上图的问题
# 3、✨GDB命令 - 启动,退出,查看
一下演示使用的test.c
文件代码如下:
#test.c
#include <stdio.h>
#include <stdlib.h>
int test(int a);
int main(int argc, char* argv[]) {
int a, b;
printf("argc = %d\n", argc);
if(argc < 3) {
a = 10;
b = 30;
} else {
a = atoi(argv[1]);
b = atoi(argv[2]);
}
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", a + b);
for(int i = 0; i < a; ++i) {
printf("i = %d\n", i);
// 函数调用c
int res = test(i);
printf("res value: %d\n", res);
}
printf("THE END !!!\n");
return 0;
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
# 3.1 启动与退出
gdb 可执行程序
quit
- 退出gdb,也可以直接写个
q
- 退出gdb,也可以直接写个
# 3.2 给程序设置参数/获取设置参数
set args 10 20
show args
- 注意:有一些程序需要设置参数,例如上述的test.c程序,main函数需要指定参数。
- 以下是普通文件传参数
# 3.3 GDB使用帮助
help
help 具体命令
如help set
- 需要加入 GDB后才能查看
# 3.4 查看当前文件代码
- 普通文件查看代码行数(还是以test.c 为例)
vim test.c
- 在命令模式下输入
:set nu
- GDB 查看当前代码
list/l
(从默认位置显示)- list默认位置显示,每次显示10行
- 蓝色框是按了回车,默认执行上一次执行的命令
list/l 行号
(从指定的行显示)- 注意: 是把行号内容放在中间显示
list/l 函数名
(从指定的函数显示)- 注意:是把函数名行放中间显示
# 3.5 查看非当前文件代码
list/l 文件名:行号
list/l 文件名:函数名
- 这里我们用的 cpp 文件,所以要使用
g++
- 进入列表后默认显示的main文件,因为我们可执行文件的入口就是main
- 两个语句的效果就是,从
mian.cpp
里跳到bubble.cpp
里的第一行,再跳到select.cpp
里的第10行
- 这里是查看
select.cpp
文件下的 selectSort 函数位置(中间显示)- 这里什么还是
select.cpp
文件,其实应该不算非当前文件跳转,不过意思知道就好
- 这里什么还是
- 第二个黄色框,我们按的回车,按理说回车应该是执行上次执行的代码,但是这里其实执行的是
list
,所以就继续往下显示
# 3.6 设置显示的行数
- 设置显示的行数
show list/listsize
set list/listsize 行数
- 这里是接着 3.5 最后图片的代码写的
- 💛: 我们在查看非当前文件代码时要加设置行数,不能直接写文件名
- 💦:查看 显示的行数
- 🪀:设置显示的行数
# 4、GDB命令 - 断点
- 设置断点
b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数
- 文件:函数名方法打的断点不是在
注意:上图最后的断点, b bubble.cpp:bubbleSort
这里的断点不是打在 定义函数这一行,而是进入这个函数体的第一行
//以下是 bullee.cpp的部分代码,可见,我们断点是打在函数体的第一个语句位置
1 #include "sort.h"
2 #include <iostream>
3
4 using namespace std;
5
6 void bubbleSort(int *array, int len) {
7
8 for (int i = 0; i < len - 1; i++) {
9 for (int j = 0; j < len - 1 - i; j++) {
10 if (array[j] > array[j + 1]) {
(gdb)
11 int temp = array[j];
12 array[j] = array[j + 1];
13 array[j + 1] = temp;
14 }
15 }
16 }
17
18 }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 查看断点
i/info b/break
- 删除断点
d/del/delete 断点编号
- 注意删除断点时使用的是断点编号
- 设置断点无效
dis/disable 断点编号
- 设置断点生效
ena/enable 断点编号
- 设置条件断点(一般用在循环的位置)
b/break 10 if i==5
# 5、GDB命令 - 调试命令
# 5.1 运行GDB程序
start(程序停在第一行)
- 想从头开始就用 start
- 执行程序入口就是
main()函数
,所以start会停在mian()这一行
run(遇到断点才停)
- 想从第一个断点开始就用 run
这里是
gdb main
启动调试程序,因为是重新启动,所以之前打的断点就都没了因为我们这里没断点,所以无论
run还是continue
都直接到完成执行了
# 5.2 继续运行,到下一个断点停
c/continue
# 5.3 向下执行一行代码(不会进入函数体)
n/next
//main.cpp 8 int array[] = {12, 27, 55, 22, 67}; 9 int len = sizeof(array) / sizeof(int); 10 11 bubbleSort(array, len); 12 //好比我们把断点打在第8行,然后我们run到这个断点,再执行三次next,会发现第二次next是跳到了 14行 //cout << "冒泡排序之后的数组: "; //而不是进入了这个函数体内,step就会进入到函数体里
1
2
3
4
5
6
7
8
9
10总结:
- next会 直接执行完函数调用
- step 会进入函数内部
# 5.4 变量操作
p/print 变量名(打印变量值)
ptype 变量名(打印变量类型)
# 5.5 向下单步调试(遇到函数进入函数体)
s/step
finish(跳出函数体)
- 注意:要跳出函数,函数内不能有断点,有断点要先删除断点再跳出
# 5.6 自动变量操作
display 变量名(自动打印指定变量的值)
i/info display
undisplay 编号
一下我们用test 来测试,所以我们这里看一下test.c 文件的代码
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int test(int a);
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int test(int a);
5
6 int main(int argc, char* argv[]) {
7 int a, b;
8 printf("argc = %d\n", argc);
9
10 if(argc < 3) {
11 a = 10;
12 b = 30;
13 } else {
14 a = atoi(argv[1]);
15 b = atoi(argv[2]);
16 }
17 printf("a = %d, b = %d\n", a, b);
18 printf("a + b = %d\n", a + b);
19
20 for(int i = 0; i < a; ++i) {
21 printf("i = %d\n", i);
22 // 函数调用
23 int res = test(i);
24 printf("res value: %d\n", res);
25 }
26
27 printf("THE END !!!\n");
28 return 0;
29 }
30
31 int test(int a) {c
32 int num = 0;
33 for(int i = 0; i < a; ++i) {
34 num += i;
35 }
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
35
36
37
38
39
40
41
- 先打几个断点 , 注意: 第二个断点是条件断点
run
到第一个断点print a
和print b
打印变量a , b 的值n
进入下一个语句,这里的语句是循环display a
和display b
将变量 a 和 b设置为自动显示
info display
查看自动变量undisplay 1
删除编号为1的 自动变量- 注意:红色框执行
next
后,没有自动显示变量是因为,next后进入了 text() 函数,函数内没有 b (应该是因为没有b?)
# 5.7 设置变量值与跳出循环
set var 变量名=变量值 (循环中用的较多)
until (跳出循环)
# 5.8 命令测试
- 先进入 gdb 打下断点,然后
run
到第一个断点处,再使用continue/c
继续运行到下一个断点
- 接上图,我们跳到了第二个断点处,即
bubble.cpp 的 bubbleSort 函数里
next
向下执行一行代码,所以到了 9step/s
向下单步调试 ,就到了 10行step
和next
很像,但是step
遇到函数会进入函数体,next
不会进入函数体
- 再次使用
continue/c
命令,跳到了第3个断点处- 可以看到这个断点是在一个for循环里,因此我们再使用
continue
命令,就是进入下次循环的当前位置 - 同时,查看断点会发现,gdb会记录断点被击中次数
- 可以看到这个断点是在一个for循环里,因此我们再使用
- 注意,下图我们击中2次后继续contimue,那就是击中3次,但是输出i=2是因为,i是从0开始的
- 此时,我们不想继续在循环里了,那就要用
until
跳出循环- 但是这里我们发现我们执行完
until
后,到了第15行,就是循环判断这一行,是因为,我们跳出循环,就是在循环判断失败时跳出
- 但是这里我们发现我们执行完
- 再执行 step,向下单步调试,又进入到了循环里,为什么呢?
- 是因为挑出循环需要满足两个条件
- 循环里面不能有断点
- 要执行循环的最后一行,就是循环判断那行(这个我们使用
until
就满足了)
- 是因为挑出循环需要满足两个条件
- 所以我们必须把循环里的断点删除(
delete/del/d 4
) ,再until
跳出循环,就成功了 - 使用
step
发现成功跳出,继续continue
,无断点了