操作系统 :Linux基础开发工具
操作系统 :Linux基础开发工具
引言
本文将带你深入了解Linux下的基础开发工具,从软件包管理器的便捷安装,到Vim编辑器的多模式高效操作;从GCC/G++编译器的底层工作原理,到Makefile的自动化构建魔法;从进度条小程序的趣味实现,到Git版本控制的团队协作精髓;最后,还会探索GDB调试器的强大功能,助你快速定位和修复代码问题。
目录
- 操作系统 :Linux基础开发工具
- 引言
- 1. 软件包管理器`yum/apt`
- 1.1 软件包
- 1.2 Linux软件生态
- 1.3 yum/apt操作
- 2. 多模式编辑器`Vim`
- 2.1 vim的基本概念及切换操作
- 2.2 vim的命令模式命令集
- 2.3 vim的底行模式命令集
- 2.3 简单vim配置
- 3. 编译器`gcc/g++`
- 3.1 `gcc`的编译选项
- 3.2 动态链接与静态链接
- 3.3 静态库与动态库
- 4. 自动化构建`make&Makefile`
- 4.1 介绍
- 4.2 使用
- 5. 实现进度条小程序
- 6. 版本控制器`Git`
- 6.1 介绍
- 7. 调试器`gdb/cgdb`
- 7.1 背景
- 7.2 使用
- 7.3 技巧
- 7.3 技巧
1. 软件包管理器yum/apt
1.1 软件包
在Linux下安装软件一般有三种方式:
- 下载到程序的源代码并进行编译,得到可执行程序(源码安装不推荐)
- 把常用的软件提前编译好,做成软件包。但是如果直接安装软件包会发生包的依赖问题(库的缺失、版本不兼容)
- 将软件包放到服务器上,并通过包管理器来管理,获取编译好的软件包进行安装。包管理器安装解决了包的依赖问题(推荐)注意:安装时必须要使用root权限,软件包安装到系统里面只需要安装一次 所有用户都可以使用。
软件包与软件包管理器的关系就好比app和应用商店的关系。
**yum(Yellow dog Updater, Modified)
**是Linux下非常常用的一种包管理器. 主要应用在Fedora,RedHat, Centos等发行版上.
Ubuntu:主要使用apt(Advanced Package Tool)
作为其包管理器。apt
同样提供了自动解决依赖关系、下载和安装软件包的功能。
1.2 Linux软件生态
Linux下载软件的过程
软件包的依赖问题
切换为国内镜像源
机器在下载软件时所使用的下载链接是操作系统内部内置链接 -> 更新apt源 -> 切换镜像源 -> 更改下载链接
镜像源:OS初始内置链接为外网官网,为了方便国内用户使用,国内云服务器将外网社区整个镜像(拷贝)过来了
增加拓展软件源epel
sudo yum install -y epel-release
1.3 yum/apt操作
关于yum的所有操作必须保证主机(虚拟机)的网络畅通!可以通过ping
指令进行验证。
查看已安装的软件包list installed
命令:yum list installed
格式:包名.架构 版本 仓库来源
已安装的软件包 acl.x86_64 2.2.51-15.el7 @base bash.x86_64 4.2.46-35.el7_9 @updates curl.x86_64 7.29.0-59.el7 @base docker-ce.x86_64 20.10.12-3.el7 @docker-ce-stable git.x86_64 1.8.3.1-24.el7_9 @updates httpd.x86_64 2.4.6-97.el7.centos.5 @updates ...
格式拆分:
acl.x86_64
:包名 + 系统架构(如 x86_64)。如果是i686
则表示32位系统安装包。2.2.51-15.el7
:版本号。el7
表示centos7/redhat7。@base
:软件来源的仓库(如base
、updates
、epel
)。
命令:apt list --installed
格式:包名/版本 架构 [状态]
acl/now 2.2.53-4 amd64 [已安装,本地] bash/now 5.1-6ubuntu1 amd64 [已安装,自动] curl/now 7.81.0-1ubuntu1.14 amd64 [已安装] docker-ce/now 5:20.10.12~3-0~ubuntu-focal amd64 [已安装] git/now 1:2.34.1-1ubuntu1.10 amd64 [已安装] apache2/now 2.4.52-1ubuntu4.6 amd64 [已安装] ...
格式拆分:
acl/now
:包名 + 版本标记(now
表示当前版本)。2.2.53-4
:版本号。amd64
:系统架构。[已安装]
:包状态(可能包含自动
标记,表示作为依赖自动安装)。
安装软件install
# Centos
$ sudo yum install -y lrzsz
# Ubuntu
$ sudo apt install -y lrzsz
- yum和apt会自动找到都有哪些软件包需要下载, 这时候按
y
确认安装.出现complete
字样, 说明安装完成。
注意:
- 安装软件时由于需要向系统⽬录中写⼊内容, ⼀般需要
sudo
或者切到 root 账⼾下才能完成。yum/apt
安装软件只能⼀个装完了再装另⼀个. 正在yum/apt
安装⼀个软件的过程中, 如果再尝试⽤yum/apt
安装另外⼀个软件,yum/apt
会报错。
卸载软件remove
# Centos
sudo yum remove [-y] lrzsz
# Ubuntu
sudo apt remove [-y] lrzsz
2. 多模式编辑器Vim
文中的“按”都可以理解为输入
2.1 vim的基本概念及切换操作
vim常用模式有三种分别是命令模式、插入模式、底行模式,各模式的功能区分如下:
- 命令/正常/普通模式(Normal mode)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入
Insert mode
下,或者到last line mode
-
插入模式(Insert mode)
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。
-
末行模式(Last Line mode)
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。
在命令模式下,输入
:
即可进入该模式,若要返回命令行模式依旧是按「ESC」键。要查看你的所有模式:打开vim,底行模式直接输入
:help vim-modes
切换操作
2.2 vim的命令模式命令集
-
进入插入模式
- 按
i
切换进入插入模式(insert mode) - 按
i
进入插入模式后,是从光标当前位置开始输入文件; - 按
a
进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字; - 按
o
进入插入模式后,是插入新的一行,从行首开始输入文字。
- 按
-
移动光标
vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母
h
,j
,k
,l
,分别控制光标左、下、上、右移一格*-
按
$
:移动到光标所在行的“行尾” -
按
^
:移动到光标所在行的“行首” -
按
w
:光标跳到下个字的开头 -
按
e
:光标跳到下个字的字尾 -
按
b
:光标回到上个字的开头 -
按
#l
:光标移到该行的第#个位置,如:5l,56l -
按
gg
:进入到文本开始 -
按
G
:进入文本末端 -
按
ctrl+b
:屏幕往“后”移动一页 -
按
ctrl+f
:屏幕往“前”移动一页 -
按
ctrl+u
:屏幕往“后”移动半页 -
按
ctrl+d
:屏幕往“前”移动半页
-
-
删除文字
-
按
x
:每按一次,删除光标所在位置的一个字符 -
按
#x
:例如,6x
表示删除光标所在位置的“后面(包含自己在内)”6个字符 -
按
X
:大写的X,每按一次,删除光标所在位置的“前面”一个字符 -
按
#X
:例如,20X
表示删除光标所在位置的“前面”20个字符 -
按
dd
:删除光标所在行 -
按
#dd
:从光标所在行开始删除#行
-
-
复制
-
按
yw
:将光标所在之处到单词尾的字符复制到缓冲区中。 -
按
#yw
:复制#个字到缓冲区。 -
按
yy
:复制光标所在行到缓冲区。 -
按
#yy
:例如,6yy
表示拷贝从光标所在的该行“往下数”6行文字。 -
按
p
:将缓冲区内的字符贴到光标所在位置。注意:所有与y
有关的复制命令都必须与p
配合才能完成复制与粘贴功能。
-
-
替换
-
按
r
:替换光标所在处的字符。 -
按
R
:替换光标所在处的字符,直到按下Esc
为止。
-
-
撤销上一次的操作
-
按
u
:如果您误执⾏⼀个命令,可以⻢上按下u
,回到上⼀个操作。按多次u
可以执行多次恢复。 -
按
ctrl + r
:撤销的恢复
-
-
更改
-
按
cw
:更改光标所在处的字到字尾处 -
按
c#w
:例如,c3w
表⽰更改3个字
-
-
跳至指定的行
-
按
Ctrl + g
:列出光标所在行的行号。 -
按
#G
:例如,15G
表示移动光标至文章的15行行首
-
2.3 vim的底行模式命令集
在使用末行模式前,请记住先按
Esc
键确定您已经处于命令模式,再按:
即可进入末行模式。
-
列出行号
- 按
set nu
:输入后会在文件中的每一行前面列出行号。
- 按
-
跳到文件中的某一行
- 按
# + 回车
:#
表示一个数字,输入完按下回车就可以跳转到第#行。
- 按
-
查找字符
-
按
/关键字
: 先按/
键,再输入您想寻找的字符,编辑器会从光标位置开始,向文件末尾方向查找匹配的字符串。如果第一次找的关键字不是您想要的,可以一直按n
会往后寻找到您要的关键字为止。 -
按
?关键字
:先按?
键,再输入您想寻找的字符,编辑器会从光标位置开始,向文件开头方向查找匹配的字符串。如果第一次找的关键字不是您想要的,可以一直按n
会往前寻找到您要的关键字为止。
-
-
保存文件
- 按
w
:在冒号输入字母w
就可以将文件保存起来。
- 按
-
离开vim
- 按
q
:按q
就是退出编辑器,如果无法离开vim,可以在q
后面跟一个!
强制离开vim。 - 按
wq
:建议使用这个命令,这样可以在退出时保存文件。
- 按
2.3 简单vim配置
配置文件的位置
- 在目录
/etc/
下面,有个名为vimrc
的文件,这是系统中公共的vim配置文件,对所有用户都有效。- 而在每个用户的主目录下,都可以自己建立私有的配置文件,命名为:
.vimrc
。例如,/root
目录下,通常已经存在一个.vimrc
文件,如果不存在,则自行创建。- 切换用户成为自己执行
su
,进入自己的主工作目录,执行cd ~
。- 打开自己目录下的
.vimrc
文件,执行vim .vimrc
。
常用配置选项
- 设置语法高亮:
syntax on
- 显示行号:
set nu
- 设置缩进的空格数为4:
set shiftwidth=4
使用插件
配置好用的vim,原生的配置可能功能不全,可以选择安装插件来完善配置。
下面给一个简单的配置插件,如果想更精进可以移步大佬的教程。
安装
TagList
插件,下载taglist_xx.zip
,解压完成,将解压出来的doc
的内容放到〜/.vim/doc
, 将解压出来的plugin
下的内容拷⻉到〜/.vim/plugin
在
〜/.vimrc
中添加:let Tlist_Show_One_File=1
letTlist_Exit_OnlyWindow=1
let Tlist_Use_Right_Window=1
安装⽂件浏览器和窗⼝管理器插件:
WinManager
下载winmanager.zip,2.X版本以上的
解压winmanager.zip,将解压出来的
doc
的内容放到〜/.vim/doc
, 将解压出来的plugin
下的内容拷贝到〜/.vim/plugin
在
〜/.vimrc
中添加let g:winManagerWindowLayout=‘FileExplorer|TagListnmap wm :WMToggle<cr>
然后重启vim,打开
~/XXX.c
或〜/XXX.cpp
, 在normal状态下输⼊wm
就可以了。
3. 编译器gcc/g++
3.1 gcc
的编译选项
格式:gcc [选项] 要编译的文件 [选项] [目标文件]
-
预处理(进行宏替换)
预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
预处理指令是以#号开头的代码行。
实例:
gcc –E test.c –o test.i
选项
-E
,该选项的作用是让 gcc 在预处理结束后停止编译过程。选项
-o
是指目标文件,需注意目标文件不是.obj
文件,.i
⽂件为已经过预处理的C原始程序。 -
编译(生成汇编)
在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。
用户可以使用
-S
选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。实例:
gcc –S test.i –o test.s
-
汇编(生成机器可识别代码)
汇编阶段是把编译阶段生成的
.s
文件转成目标文件读者在此可使用选项
-c
就可看到汇编代码已转化为.o
的⼆进制目标代码了实例:
gcc –c test.s –o test.o
-
链接(生成可执行文件或库文件)
在成功编译后,就进入了链接阶段
示例:
gcc test.o -o test
-
为什么C/C++编译,要先形成汇编
编译器的语言自举过程,在语言的更新迭代中,每个语言都实现自己的高级语言->机器语言(二进制)太麻烦了。但是如果只实现自己的高级->汇编,就可以借用之前的汇编->机器。
-
条件编译的用途
- 软件进行专业度,收费情况进行区分(业务),使用条件编译,可以进行代码动态裁剪
- 内核源代码也是采用条件编译进行代码裁剪
- 开发工具,应用软件
-
gcc
其他常用选项-
-E
只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面 -
-S
编译到汇编语言不进行汇编和链接 -
-c
编译到目标代码 -
-o
文件输出到文件 -
-static
此选项对生成的文件采用静态链接 -
-g
生成调试信息。GNU 调试器可利用该信息。 -
-shared
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库. -
-O0
,-O1
,-O2
,-O3
编译器的优化选项的4个级别,-O0
表示没有优化,-O1
为缺省值,-O3
优化级别最⾼ -
-w
不生成任何警告信息。 -
-Wall
生成所有警告信息。
-
3.2 动态链接与静态链接
在我们的实际开发中,不可能将所有的代码放在一个源文件中,而是多个源文件,多个源文件之间不是独立的,而是存在多种依赖关系。但是每个源文件都是独立编译的,为了满足前面的依赖关系,就需要将这些源文件产生的目标文件进行连接,从而形成一个可执行的程序。这个链接过程就是静态链接。
静态链接的缺点:
浪费空间:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用
printf()
函数,那么这多个程序中就都会含有printf.o
,所以同一个目标文件会在内存中存在多个副本。更新比较困难:因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接的出现解决了静态链接出现的问题。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
3.3 静态库与动态库
-
库的概念:
我们的C程序中,并没有定义
printf
的函数实现,且在预编译中包含的stdio.h
中也只有该函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实现printf
函数的呢?最后的答案是:系统把这些函数实现都被做到名为
libc.so.6
的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径/usr/lib
下进⾏查找,也就是链接到libc.so.6
库函数中去,这样就能实现函数printf
了,而这也就是链接的作⽤。库是一套方法或者数据集,为我们开发提供最基本的保证(基本接口,功能,加速我们二次开发)
-
静态库:
静态库是指编译链接时,把库文件的代码全部加⼊到可执⾏文件中(把我们程序中会使用到的库方法,拷贝给自己),因此⽣成的文件比较大,但在运⾏时也就不再需要库文件了。也可以理解为静态库只有在链接时有用,一旦形成可执行程序,静态库可以不再需要。
-
动态库:
动态库与之相反,在编译链接时并没有把库文件的代码加⼊到可执⾏文件中,而是在程序运⾏时链接文件加载库(把我们程序中会使用到的库方法和动态库内部实现的方法连接起来,能让我们自己的程序能在库中找到方法。执行目标方法需要翻转到库中执行,完成在返回),这样可以节省系统的开销。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执⾏文件,如下所示
gcc hello.o -o hello
。 -
文件后缀名:
Linux下的文件后缀名:动态库
XXX.so
, 静态库XXX.a
。Windows下的文件后缀名:动态库
XXX.dll
, 静态库XXX.lib
。
4. 自动化构建make&Makefile
4.1 介绍
-
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,
makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。 -
makefile
带来的好处就是——“自动化编译”,一旦写好,只需要一个make
命令,整个工程完全自动编译,极大的提高了软件开发的效率。 -
make
是一个命令工具,是一个解释makefile
中指令的命令工具,makefile
是一个文件,两个搭配使用,完成项目自动化构建。
4.2 使用
-
Makefile文件的构成
需要被编译的代码
#include <stdio.h> int main() { printf("hello Makefile!\n"); return 0; }
Makefile文件
myproc:myproc.c gcc -o myproc myproc.c .PHONY:clean clean: rm -f myproc
一个Makefile文件由3部分组成:依赖关系、依赖方法、项目清理
依赖关系:上面的文件
myproc
依赖myproc.c
依赖方法:
gcc -o myproc myproc.c
,就是与依赖关系对应的依赖方法项目清理:工程是需要被清理的。像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行(默认老代码不做重编译,因为make通过文件的Modify时间来判断
bin
和.c
的新旧问题),不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。但是一般我们这种clean的目标文件,我们将它设置为伪目标,用.PHONY
修饰,伪目标的特性是,总是被执行的。.PHONY
原理:让make忽略源文件和可执行文件的M时间对比 -
使用原理讲解
myproc:myproc.o gcc myproc.o -o myproc myproc.o:myproc.s gcc -c myproc.s -o myproc.o myproc.s:myproc.i gcc -S myproc.i -o myproc.s myproc.i:myproc.c gcc -E myproc.c -o myproc.i .PHONY:clean clean: rm -f *.i *.s *.o myproc
make的工作流程
make会在当前目录下找名字叫
Makefile
或makefile
的文件。make扫描Makefile文件的时候,从上向下扫描,默认形成第一个目标文件。如果找到,它会找文件中的第⼀个目标文件(target),在上面的例子中,他会找到
myproc
这个文件,并把这个文件作为最终的目标文件。如果
myproc
文件不存在,或是myproc
所依赖的后面的myproc.o
文件的文件修改时间要比myproc
这个文件新(可以用touch测试),那么,他就会执行后面所定义的命令来生成myproc
这个文件。如果
myproc
所依赖的myproc.o
文件不存在,那么make
会在当前文件中找目标为myproc.o
文件的依赖性,如果找到则再根据那一个规则⽣成myproc.o
文件。(这有点像一个堆栈的过程)当然,你的C文件和H文件是存在的啦,于是 make 会生成
myproc.o
文件,然后再用myproc.o
文件生成 make 的终极任务,也就是执行文件myproc
了。这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,就不工作了。
-
拓展语法
BIN=proc.exe # 定义变量 CC=gcc #SRC=$(shell ls *.c) # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名 SRC=$(wildcard *.c) # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名 OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表 LFLAGS=-o # 链接选项 FLAGS=-c # 编译选项 RM=rm -f # 引⼊命令 $(BIN):$(OBJ) @$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表 @echo "linking ... $^ to $@" %.o:%.c # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同名.o @$(CC) $(FLAGS) $< # %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。 @echo "compling ... $< to $@" # @:不回显命令 .PHONY:clean clean: $(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它 .PHONY:test test: @echo $(SRC) @echo $(OBJ)
5. 实现进度条小程序
小知识:
行缓冲区就是一段内存块;刷新缓冲区:
fflush(stdout)
printf
会将内容放到缓冲区中,只有刷新缓冲区才能打印到屏幕中。显示器是字符设备,因此只认识字符。
源代码
processbar.c
文件
#include "process.h" #include <string.h> #include <unistd.h> #define NUM 101 #define STYLE '=' // verison2 void FlushProcess(double total, double current) { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable="|/-\\"; int len = strlen(lable); static int cnt = 0; // 不需要自己循环,填充# int num = (int)(current*100/total); // 11.0 / 1000 int i = 0; for(; i < num; i++) { buffer[i] = STYLE; } double rate = current/total; cnt %= len; printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]); cnt++; fflush(stdout); } // 无法使用 void process_v1() { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable="|/-\\"; int len = strlen(lable); int cnt = 0; while(cnt <= 100) { printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]); fflush(stdout); buffer[cnt]= STYLE; cnt++; usleep(50000); } printf("\n"); }
-
processbar.h
文件#pragma once #include <stdio.h> void process_v1(); void FlushProcess(double total, double current);
-
main.c
文件#include "process.h" #include <stdio.h> typedef void (*callback_t)(double total, double current); double total = 1024.0; double speed = 1.0; // 回调函数 void DownLoad(callback_t cb) { double current = 0; while(current <= total) { cb(total, current); // 下载代码 usleep(3000); // 充当下载数据 current += speed; } printf("\ndownload %.2lfMB Done\n", current); } void UpLoad(callback_t cb) { double current = 0; while(current <= total) { cb(total, current); // 下载代码 usleep(3000); // 充当下载数据 current += speed; } printf("\nupload %.2lfMB Done\n", current); } int main() { DownLoad(FlushProcess); DownLoad(FlushProcess); UpLoad(FlushProcess); return 0; }
-
Makefile
文件SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o) BIN=processbar $(BIN):$(OBJ) gcc -o $@ $^ %.o:%.c gcc -c $< #-g .PHONY: clean: rm -f $(OBJ) $(BIN)
效果图
6. 版本控制器Git
6.1 介绍
版本控制器就是能让你了解到⼀个文件的历史以及它的发展过程的系统,通俗的讲就是⼀个可以记录工程的每⼀次改动和版本迭代的⼀个管理系统,同时也⽅便多⼈协同作业。
在这里只讲述关于git使用的一些小知识,关于详细的配置请自行查找教程。
- git命令行安装:
sudo yum install git
或sudo apt install -y git
- 查看git版本:
git --version
- 使用
.gitignore
再上传文件时可以忽略特定后缀的文件。- 添加
add
、提交commit
、上传push
、同步pull
- git提交的时候,只会提交变化的部分。
- git是去中心化,分布式的版本控制器。
- git是一个本地客户端,也是一个云端服务器。
add
只是将文件放到暂存区、commit
提交到本地git仓库、push
上传到云端git仓库、pull
同步云端git仓库- 如果
push
时显示冲突了,就是在提醒本地用户,你需要先和远端仓库进行同步pull
。
7. 调试器gdb/cgdb
7.1 背景
程序的发布方式有两种,debug
模式和release
模式。LInux中的gcc/g++
编译出来的二进制程序,默认是release
模式。要使用gdb
调试,必须在源代码生成二进制程序的时候,加上-g
选项,如果没有添加,程序无法被编译。
7.2 使用
- 开始:
gdb binFile
- 退出:
Ctrl + d
或quit
调试命令
命令 | 作用 | 样例 |
---|---|---|
list/l | 显示源代码,从上次位置开始,每次列出10行 | list/l 10 |
list/l 函数名 | 列出指定函数的源代码 | list/l main |
list/l 文件名:行号 | 列出指定文件的源代码 | list/l mycmd.c:1 |
r/run | 从程序开始连续执行 | run |
n/next | 单步执行,不进入函数内部 | next |
s/step | 单步执行,进入函数内部 | step |
break/b [文件名:]行号 | 在指定行号设置断点 | break 10 ,break test.c:10 |
break/b 函数名 | 在函数开头设置断点 | break main |
info break/b | 查看当前所有断点的信息 | info break |
finish | 执行到当前函数返回,然后停止(确定问题是否在函数内) | finish |
print/p 表达式 | 打印表达式的值 | print start+end |
p 变量 | 打印指定变量的值 | p x |
set var 变量=值 | 修改变量的值 | set var i=10 |
continue/c | 从当前位置开始连续执行程序 | continue |
delete/d ,breakpoints | 删除所有断点 | delete breakpoints |
delete/d ,breakpoints n | 删除序号为n的断点 | delete breakpoints 1 |
disable breakpoints | 禁用所有断点 | disable breakpoints |
enable breakpoints | 启用所有断点 | enable breakpoints |
info/i breakpoints | 查看当前设置的断点列表 | info breakpoints |
display 变量名 | 跟踪显示指定变量的值(每次停止时) | display x |
undisplay 编号 | 取消对指定编号的变量的跟踪显示 | undisplay 1 |
until X⾏号 | 执行到指定行号(局部区域快速执行) | until 20 |
backtrace/bt | 查看当前执行栈的各级函数调用及参数 | backtrace |
info/i locals | 查看当前栈帧的局部变量值 | info locals |
quit | 退出GDB调试器 | quit |
7.3 技巧
-
安装
cgdb
可以看到代码调试
Ubuntu:
sudo apt-fet install -y cgdb
Centos:
sudo yum install -y cgdb
-
watch
执行时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运行期间的值发生变化,GDB 会暂停程序的执行,并通知使用者
-
set var
可以在不修改源代码的情况下,预设变量的值以便调试
-
条件断点
- 条件断点添加常见两种方式:1. 新增 2. 给已有断点追加
- 注意两者的语法有区别,不要写错了
- 新增:
b 行号 / 文件名:行号 / 函数名 if i == 30(条件)
,行号为断点所在行。 - 给已有断点追加:
condition 2 i==30
,其中2
是已有断点编号,没有if
Cgdb
分屏操作ESC
进入代码屏,i
回到gdb
屏
|backtrace
|
|info/i locals
| 查看当前栈帧的局部变量值 |info locals
|
|quit
| 退出GDB调试器 |quit
|
7.3 技巧
-
安装
cgdb
可以看到代码调试
Ubuntu:
sudo apt-fet install -y cgdb
Centos:
sudo yum install -y cgdb
-
watch
执行时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运行期间的值发生变化,GDB 会暂停程序的执行,并通知使用者
-
set var
可以在不修改源代码的情况下,预设变量的值以便调试
-
条件断点
- 条件断点添加常见两种方式:1. 新增 2. 给已有断点追加
- 注意两者的语法有区别,不要写错了
- 新增:
b 行号 / 文件名:行号 / 函数名 if i == 30(条件)
,行号为断点所在行。 - 给已有断点追加:
condition 2 i==30
,其中2
是已有断点编号,没有if
Cgdb
分屏操作ESC
进入代码屏,i
回到gdb
屏