news 2026/6/10 7:30:20

GDB 调试工具入门教程:从零开始手把手教你调试 C/C++ 程序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GDB 调试工具入门教程:从零开始手把手教你调试 C/C++ 程序

一、前言

在学习 C/C++、Linux 编程、嵌入式开发、ROS 开发或者操作系统相关课程时,很多同学都会遇到一个问题:

程序能编译,但是运行结果不对;
程序运行一半突然崩溃;
出现 Segmentation fault,却不知道错在哪里。

如果只靠printf一行一行打印变量,虽然也能排查问题,但效率很低。尤其当程序结构复杂、函数调用层级很多时,单纯依赖打印会变得非常麻烦。

这时就需要使用专业的调试工具:GDB

GDB 是 Linux 环境下非常常用的调试器,它可以让程序在指定位置暂停,查看变量值,单步执行代码,进入函数内部,查看函数调用栈,还能定位段错误等问题。

本文将从零开始,带你一步一步学习 GDB 的基本使用方法。


二、GDB 是什么?

GDB 的全称是GNU Debugger,中文通常叫做 GNU 调试器。

它主要用于调试 C、C++ 等程序。

我们可以这样理解:

平时运行程序时,程序是从头到尾连续执行的,中间发生了什么我们看不到。而使用 GDB 后,我们可以控制程序的执行过程,让它:

  1. 在某一行代码处停下来;
  2. 一行一行地执行;
  3. 查看变量当前的值;
  4. 查看函数是如何调用的;
  5. 查看程序崩溃在哪一行;
  6. 修改程序运行过程中的变量值;
  7. 分析段错误、死循环、数组越界等问题。

简单来说:

GDB 的作用就是让程序“慢下来”,让我们看清楚程序每一步到底做了什么。


三、安装 GDB

如果你使用的是 Ubuntu 或 Debian 系统,可以使用下面命令安装:

sudoaptupdatesudoaptinstallgdb

安装完成后,查看版本:

gdb--version

如果能看到类似下面的信息,说明安装成功:

GNU gdb(Ubuntu 版本号)Copyright(C)Free Software Foundation, Inc.

四、准备一个测试程序

为了方便学习,我们先写一个简单的 C 程序。

创建一个文件夹:

mkdirgdb_democdgdb_demo

新建源文件:

vimmain.c

如果不会使用vim,也可以用nano

nanomain.c

输入下面代码:

#include<stdio.h>intadd(inta,intb){intresult=a+b;returnresult;}intmain(){intx=10;inty=20;intsum=add(x,y);printf("sum = %d\n",sum);return0;}

这段代码很简单,主要功能是:

  1. main函数中定义两个整数xy
  2. 调用add函数计算两个数的和;
  3. 将结果保存到sum
  4. 最后打印结果。

五、编译程序:一定要加-g

普通编译命令是:

gcc main.c-omain

这样虽然可以生成可执行文件,但不适合调试。

如果要使用 GDB 调试,编译时必须加上-g参数:

gcc-gmain.c-omain

这里的-g表示加入调试信息。

调试信息中包含:

  1. 源代码文件名;
  2. 代码行号;
  3. 函数名;
  4. 变量名;
  5. 类型信息。

如果没有-g,GDB 仍然可以打开程序,但是调试体验会很差,可能看不到源码,也不能方便地查看变量。

为了让调试结果更准确,建议初学时使用:

gcc-g-O0main.c-omain

其中:

-g表示加入调试信息-O0表示关闭编译优化

为什么要关闭优化?

因为编译器优化后,代码的实际执行顺序可能和源代码不完全一致,某些变量也可能被优化掉。对于初学者来说,关闭优化更容易理解程序执行过程。

运行程序:

./main

输出结果:

sum=30

六、启动 GDB

使用下面命令启动 GDB:

gdb ./main

进入 GDB 后,会看到类似下面的提示:

(gdb)

这说明你已经进入了 GDB 的命令环境。

在这个环境中,我们输入的不再是普通 Linux 命令,而是 GDB 的调试命令。


七、GDB 调试的基本流程

GDB 的基本调试流程一般是:

1. 启动 GDB 2. 设置断点 3. 运行程序 4. 程序停在断点处 5. 单步执行代码 6. 查看变量 7. 继续运行或退出

下面我们一步一步来操作。


八、设置断点

断点的作用是:

让程序运行到指定位置时自动暂停。

例如我们想让程序一进入main函数就暂停,可以输入:

break main

也可以使用简写:

b main

GDB 可能会显示:

Breakpoint1at 0x0000000000001149:filemain.c, line10.

这句话表示:

  1. 成功设置了一个断点;
  2. 断点编号是 1;
  3. 断点位置在main.c文件;
  4. 对应源代码第 10 行。

除了按函数名设置断点,也可以按行号设置断点。

例如:

break 14

或者:

b main.c:14

表示在main.c的第 14 行设置断点。


九、查看源码

在 GDB 中可以使用list命令查看源码:

list

简写为:

l

如果想查看main函数附近的代码:

list main

如果想查看第 12 行附近的代码:

list 12

GDB 会显示类似:

89intmain()10{11intx=10;12inty=20;1314intsum=add(x,y);1516printf("sum = %d\n",sum);17

这样就可以确认断点位置是否正确。


十、运行程序

设置好断点后,输入:

run

也可以简写:

r

程序开始运行,并在main函数处停下来。

你可能会看到:

Breakpoint1, main()at main.c:1111int x=10;

这句话很重要,意思是:

程序现在停在main.c第 11 行,这一行代码即将执行,但还没有执行。

也就是说,此时int x = 10;还没有真正执行。


十一、单步执行:next 命令

如果想让程序执行当前这一行,然后停到下一行,可以使用:

next

简写为:

n

例如当前停在:

intx=10;

输入:

n

程序会执行这一行,然后停到下一行:

inty=20;

这个时候,变量x已经被赋值为 10。


十二、查看变量:print 命令

查看变量值使用:

print 变量名

简写为:

p 变量名

例如查看x

p x

输出可能是:

$1=10

这里的$1是 GDB 给这次输出结果起的编号,不是变量名。

继续执行一行:

n

然后查看y

p y

输出:

$2=20

查看sum

p sum

如果此时sum那一行还没有执行,它的值可能是不确定的。因为变量虽然已经声明,但还没有完成赋值。

所以要记住一个原则:

GDB 显示的当前行通常是“即将执行的行”,不是“已经执行完的行”。


十三、进入函数内部:step 命令

现在程序会执行到这一行:

intsum=add(x,y);

这里调用了add函数。

如果使用:

n

GDB 会把这一行整体执行完,不会进入add函数内部。

如果想进入add函数中查看执行过程,需要使用:

step

简写为:

s

当程序停在:

intsum=add(x,y);

输入:

s

程序会进入add函数:

intadd(inta,intb){intresult=a+b;returnresult;}

此时可以查看函数参数:

p a p b

输出:

$3=10$4=20

说明main函数中的xy已经传给了add函数的参数ab


十四、next 和 step 的区别

初学 GDB 时,很多人会分不清nextstep

它们的区别如下:

命令简写作用
nextn执行下一行,遇到函数不会进入
steps执行下一步,遇到函数会进入函数内部

举例:

intsum=add(x,y);

如果使用:

n

GDB 会直接执行完整个add(x, y),然后停到下一行。

如果使用:

s

GDB 会进入add函数内部,让你看到函数里面是怎么执行的。

简单记忆:

next:看表面 step:进里面

十五、跳出当前函数:finish 命令

当我们进入add函数后,如果不想一行一行执行了,可以使用:

finish

这个命令的作用是:

执行完当前函数,并回到调用这个函数的位置。

例如在add函数中输入:

finish

GDB 可能显示:

Run tillexitfrom#0 add (a=10, b=20) at main.c:6Value returned is$5=30

这表示add函数执行结束,返回值是 30。


十六、继续运行:continue 命令

如果程序停在某个断点,或者单步执行过程中暂停了,你想让程序继续运行,可以输入:

continue

简写:

c

程序会继续运行,直到:

  1. 遇到下一个断点;
  2. 程序正常结束;
  3. 程序发生错误;
  4. 用户手动中断。

在当前例子中,如果没有其他断点,输入:

c

程序会继续执行并输出:

sum=30

然后程序结束。


十七、查看所有断点

查看当前设置了哪些断点,可以使用:

info breakpoints

也可以简写:

info b

显示结果类似:

Num Type Disp Enb Address What1breakpoint keep y 0x0000000000001149inmain at main.c:11

其中:

字段含义
Num断点编号
Type类型
Enb是否启用
What断点位置

十八、删除断点

删除指定断点:

delete 1

也可以简写:

d 1

这里的1是断点编号。

如果想删除所有断点:

delete

GDB 会询问是否确认删除。


十九、禁用和启用断点

有时我们不想删除断点,只是暂时不用它,可以禁用断点:

disable 1

重新启用:

enable 1

这样做的好处是,调试复杂程序时不需要频繁删除和重新设置断点。


二十、自动显示变量:display 命令

如果每执行一步都要手动输入:

p x

会比较麻烦。

这时可以使用display命令,让 GDB 每次暂停时自动显示变量值。

例如:

display x display y display sum

之后每次执行:

n

GDB 都会自动显示这些变量的值。

查看已经设置的自动显示项:

info display

取消某个自动显示项:

undisplay 1

其中1是 display 编号。


二十一、查看局部变量

如果当前函数里有很多局部变量,一个一个print会比较麻烦。

可以使用:

info locals

这个命令会显示当前函数中的所有局部变量。

例如在main函数中,可能会显示:

x=10y=20sum=30

查看当前函数参数:

info args

例如在add函数中,可能会显示:

a=10b=20

二十二、查看函数调用栈:backtrace 命令

函数调用栈可以帮助我们知道:

当前代码是从哪些函数一步一步调用过来的。

使用命令:

backtrace

简写:

bt

如果当前程序停在add函数中,输入:

bt

可能会看到:

#0 add (a=10, b=20) at main.c:5#1 main () at main.c:14

这表示:

  1. 当前正在执行的是add函数;
  2. add函数是被main函数调用的;
  3. 调用位置在main.c第 14 行。

调用栈在调试复杂程序时非常重要,尤其是程序崩溃时,可以通过bt快速看到崩溃是从哪里一路调用过来的。


二十三、切换栈帧:frame 命令

当使用bt查看调用栈后,可以使用frame命令切换到不同的函数调用层级。

例如:

#0 add (a=10, b=20) at main.c:5#1 main () at main.c:14

如果当前在#0,也就是add函数中。

切换到main函数:

frame 1

简写:

f 1

然后可以查看main函数中的变量:

p x p y p sum

如果想回到add函数:

frame 0

二十四、调试段错误 Segmentation fault

GDB 最常用的场景之一,就是排查段错误。

下面写一个会产生段错误的程序。

创建文件:

vimseg.c

输入代码:

#include<stdio.h>intmain(){int*p=NULL;*p=100;printf("value = %d\n",*p);return0;}

编译:

gcc-g-O0seg.c-oseg

直接运行:

./seg

程序会报错:

Segmentation fault

这说明程序访问了非法内存。

接下来用 GDB 调试:

gdb ./seg

运行:

run

GDB 会提示:

Program received signal SIGSEGV, Segmentation fault. main()at seg.c:77*p=100;

这说明程序在第 7 行崩溃:

*p=100;

查看指针p

p p

输出:

$1=(int *)0x0

0x0就是空地址,也就是NULL

所以错误原因是:

指针 p 是空指针,却对它进行了解引用操作。

错误代码:

int*p=NULL;*p=100;

正确写法之一:

intvalue=0;int*p=&value;*p=100;

完整修改后:

#include<stdio.h>intmain(){intvalue=0;int*p=&value;*p=100;printf("value = %d\n",*p);return0;}

二十五、调试数组越界问题

再看一个常见错误:数组越界。

创建文件:

vimarray.c

输入代码:

#include<stdio.h>intmain(){intarr[3]={1,2,3};for(inti=0;i<=3;i++){printf("arr[%d] = %d\n",i,arr[i]);}return0;}

编译:

gcc-g-O0array.c-oarray

使用 GDB:

gdb ./array

设置断点:

b main

运行:

r

单步执行:

n n

当进入循环后,可以查看i

p i

查看数组:

p arr

查看指定元素:

p arr[0] p arr[1] p arr[2] p arr[3]

数组定义是:

intarr[3]={1,2,3};

它只有 3 个元素,下标分别是:

arr[0] arr[1] arr[2]

但是循环条件写成了:

i<=3

i等于 3 时,会访问:

arr[3]

这就是数组越界。

正确写法应该是:

for(inti=0;i<3;i++)

所以这类问题的调试思路是:

  1. 在循环处设置断点;
  2. 单步执行;
  3. 查看循环变量;
  4. 查看数组下标;
  5. 判断是否访问了非法位置。

二十六、调试死循环

死循环也是常见问题。

创建文件:

vimloop.c

输入代码:

#include<stdio.h>intmain(){inti=0;while(i<5){printf("i = %d\n",i);}return0;}

编译:

gcc-g-O0loop.c-oloop

运行:

./loop

你会发现程序一直输出:

i=0i=0i=0

程序不会停止。

这时用 GDB 调试:

gdb ./loop

运行:

run

如果程序一直执行,可以按:

Ctrl + C

这会让 GDB 暂停正在运行的程序。

暂停后输入:

bt

查看程序当前停在哪里。

再输入:

list

查看附近代码。

可以看到循环部分:

while(i<5){printf("i = %d\n",i);}

问题是变量i一直没有变化,所以i < 5永远成立。

正确写法:

while(i<5){printf("i = %d\n",i);i++;}

二十七、条件断点

如果程序中有一个循环执行很多次,我们不想每次循环都停,只想在某个条件满足时停,可以使用条件断点。

例如:

#include<stdio.h>intmain(){for(inti=0;i<10;i++){printf("i = %d\n",i);}return0;}

编译:

gcc-g-O0condition.c-ocondition

进入 GDB:

gdb ./condition

如果想让程序在i == 5时停下,可以设置条件断点:

b 7 if i == 5

这里假设第 7 行是:

printf("i = %d\n",i);

运行:

r

程序会在i等于 5 的时候暂停。

条件断点适合调试:

  1. 循环次数很多的程序;
  2. 某个变量达到特定值才出错的程序;
  3. 数组处理、链表遍历、数据查找等场景。

二十八、监视变量变化:watch 命令

有时候我们会遇到这种问题:

某个变量的值突然变错了,但不知道是哪一行代码改坏的。

这时可以使用watch命令。

例如:

watch sum

表示监视变量sum

只要sum的值发生变化,程序就会自动暂停。

例如:

intsum=0;sum=10;sum=20;

如果设置了:

watch sum

sum被修改时,GDB 会停下来,并告诉你变量旧值和新值。

这对排查变量被意外修改的问题非常有用。


二十九、修改变量值

GDB 不仅能查看变量值,还能在程序运行过程中修改变量。

例如当前程序中:

intx=10;inty=20;

在 GDB 中可以输入:

set var x = 100

然后查看:

p x

输出:

$1=100

这说明变量x已经被修改了。

这种功能适合测试不同分支,比如:

if(score>=60){printf("pass\n");}else{printf("fail\n");}

你可以在 GDB 中直接修改:

set var score = 59

或者:

set var score = 90

这样不用反复改代码、重新编译,就可以测试不同情况。


三十、调试带命令行参数的程序

有些程序运行时需要参数,例如:

./main hello world

下面写一个示例程序。

创建文件:

vimargs.c

输入:

#include<stdio.h>intmain(intargc,char*argv[]){printf("argc = %d\n",argc);for(inti=0;i<argc;i++){printf("argv[%d] = %s\n",i,argv[i]);}return0;}

编译:

gcc-g-O0args.c-oargs

普通运行:

./args hello world

使用 GDB 调试时,有两种传参方式。

第一种,直接在run后面加参数:

run hello world

第二种,先设置参数:

set args hello world run

查看当前参数:

show args

三十一、查看内存

GDB 还可以查看内存内容,常用命令是:

x

x是 examine 的意思,表示检查内存。

常见格式:

x/数量格式单位 地址

例如:

x/4xw &x

含义如下:

部分含义
xexamine,查看内存
/4查看 4 个单位
x用十六进制显示
wword,4 字节
&x变量 x 的地址

常见显示格式:

格式含义
x十六进制
d有符号十进制
u无符号十进制
c字符
s字符串
i汇编指令

例如查看变量地址:

p &x

查看变量附近的内存:

x/4xw &x

查看字符串:

x/s str

查看数组中的 10 个整数:

x/10dw arr

这里:

10 表示查看 10 个 d 表示十进制 w 表示每个单位 4 字节

三十二、查看汇编代码

如果学习操作系统、嵌入式、汇编或底层调试,可以使用 GDB 查看汇编代码。

反汇编当前函数:

disassemble

反汇编指定函数:

disassemble main

显示当前指令:

x/i $pc

其中$pc表示当前程序计数器,也就是 CPU 正在执行的位置。

如果想让 GDB 显示源码窗口,可以使用:

layout src

显示汇编窗口:

layout asm

退出这个界面:

Ctrl + X 然后按 A

也就是先按住Ctrl再按X,松开后再按A


三十三、常见 GDB 报错和解决方法

1. No debugging symbols found

如果启动 GDB 时看到:

No debugging symbols found

说明编译时没有加-g

错误编译方式:

gcc main.c-omain

正确编译方式:

gcc-g-O0main.c-omain

2. No symbol table is loaded

这个问题通常也是因为没有调试信息。

解决方法:

gcc-g-O0main.c-omain gdb ./main

3. 程序没有停在断点

可能原因有:

  1. 断点所在代码根本没有被执行;
  2. 没有加-g
  3. 编译时开启了优化;
  4. GDB 加载的不是刚刚编译的程序。

建议重新编译:

gcc-g-O0main.c-omain

4. Cannot access memory at address 0x0

这个错误通常和空指针有关。

例如:

int*p=NULL;*p=10;

解决方法是:在使用指针前,确保它指向有效地址。


5. Segmentation fault

段错误常见原因包括:

  1. 空指针解引用;
  2. 数组越界;
  3. 使用已经释放的内存;
  4. 栈溢出;
  5. 字符串越界写入;
  6. 访问非法地址。

遇到段错误时,最推荐的做法是:

gdb ./程序名

然后:

run bt

通常可以直接定位到出错位置。


三十四、GDB 常用命令速查表

命令简写作用
gdb ./main启动 GDB
break mainb main在 main 函数设置断点
break 10b 10在第 10 行设置断点
runr运行程序
nextn单步执行,不进入函数
steps单步执行,进入函数
continuec继续运行
print xp x查看变量 x
display x每次暂停自动显示 x
info locals查看局部变量
info args查看函数参数
backtracebt查看函数调用栈
frame 1f 1切换到第 1 层栈帧
finish执行完当前函数并返回
info breakpointsinfo b查看所有断点
delete 1d 1删除编号为 1 的断点
disable 1禁用编号为 1 的断点
enable 1启用编号为 1 的断点
watch x监视变量 x 的变化
set var x=10修改变量 x 的值
listl查看源码
quitq退出 GDB
最后再记住一句话:

GDB 调试的核心不是背命令,而是看清楚程序每一步执行到了哪里、变量发生了什么变化、错误是从哪一步开始出现的。

只要掌握了这个思路,后面无论是调试 C/C++ 程序、Linux 程序、嵌入式程序,还是 ROS 工程,GDB 都会成为非常有用的工具。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 7:24:10

DGX系列有铁芯直线电机模组结构与性能分析

能点到点快速定位型有铁芯直线电机模组&#xff0c;重复定位精度达微米级&#xff0c;适用于对定位速度与精度均有要求的自动化应用场景。模组采用有铁芯直线电机。有铁芯结构磁路磁阻低&#xff0c;同体积下推力密度高于无铁芯方案&#xff0c;在重负载高速运动中优势明显。该…

作者头像 李华
网站建设 2026/6/10 7:24:08

openEuler安装MongoDB指导

openEuler安装MongoDB 8.2.7 实验报告 0 软件版本 > Linux版本&#xff1a;OpenEuler 24.03 SP2 LTS > Hadoop版本&#xff1a;hadoop3.4.1 > HBase版本&#xff1a;hbase2.6.4 > MongoDB版本&#xff1a;8.2.7 > MongoDB Compass版本&#xff1a;1.45.4…

作者头像 李华
网站建设 2026/6/10 7:22:45

崇义禄安酒店管理有限公司

崇义禄安酒店管理有限公司简介崇义禄安酒店管理有限公司是一家专注于酒店运营与管理的企业&#xff0c;业务可能涵盖酒店投资、品牌管理、客房服务、餐饮运营等领域。公司具体信息需结合工商注册或公开资料进一步确认。

作者头像 李华
网站建设 2026/6/10 7:21:45

粤嵌GEC6818开机后显示自定义图片

​ 第一步&#xff1a;在电脑上将图片修改为屏幕对应分辨率并转换为PPM格式 调节图片大小尺寸&#xff0c;推荐在线网站&#xff1a;在线调整图片尺寸&#xff1b; 由于常见的 Windows 绘图工具&#xff08;如画图&#xff09;无法直接另存为 PPM 格式&#xff0c;推荐直接使…

作者头像 李华