GDB
GDB 调试入门
一、为什么要学习调试?
写程序时,错误不一定都是语法错误。很多时候,程序能够运行,但是结果不对,这类错误叫做逻辑错误。
普通运行程序时,我们只能看到最后的输入和输出,看不到程序中间是怎么执行的。调试工具的作用,就是让程序”慢下来”,帮助我们一行一行观察程序的执行过程。
二、GDB 和 OnlineGDB 是什么?
GDB 是常用的程序调试工具,可以帮助我们设置断点、单步执行、查看变量值。
OnlineGDB 是一个在线编程和调试网站,不需要本地安装环境,适合课堂演示。
课堂演示网址:
https://www.onlinegdb.com/
进入网站后,左上角语言选择:
C++17 或 C++20
三、断点是什么?
断点就是让程序运行到某一行时先暂停。
注意:断点不是程序的起点。程序仍然会从 main 函数开始运行,只是运行到断点所在行时暂停。
程序从
main 开始执行 → 正常运行前面的代码 → 到达断点所在行 → 暂停 → 开始观察变量和单步调试。
例如:
int n;
cin >> n;
int sum = 0; // 如果这里打断点,程序会运行到这一行暂停
for (int i = 1; i <= n; i++) {
sum += i;
}
cout << sum << endl;
如果断点打在 int sum = 0; 这一行,那么 cin >> n; 已经执行过了,所以 n 已经有输入值。
四、GDB 常用命令速查表
| GDB 命令 | 简称 | 作用 | OnlineGDB 对应操作 |
|---|---|---|---|
break |
b |
在指定行设置断点 | 点击行号左侧 |
next |
n |
单步执行(不进入函数内部) | Step Over 按钮 |
step |
s |
单步执行(进入函数内部) | Step Into 按钮 |
continue |
c |
继续运行到下一个断点或程序结束 | Continue 按钮 |
print |
p |
查看变量的当前值 | Variables 面板自动显示 |
display |
– | 每次暂停时自动显示某个变量的值 | 在 Variables 面板中固定观察 |
list |
l |
显示当前暂停位置附近的源代码 | 代码编辑区自动高亮 |
info locals |
– | 显示当前作用域所有局部变量 | Variables 面板自动列出 |
quit |
q |
退出 GDB | 点击 Stop 或关闭页面 |
五、OnlineGDB 演示流程
- 打开
https://www.onlinegdb.com/。 - 语言选择 C++17 或 C++20。
- 输入示例代码。
- 先点击 Run 普通运行,观察最终输出。
- 在代码左侧行号处点击,设置断点。
- 点击 Debug 进入调试模式。
- 使用 Step Over 一行一行执行。
- 观察变量
i、sum、a[i]等的变化。 - 使用 Continue 继续运行到下一个断点或程序结束。
六、第一个演示:累加求和
下面的程序计算 1 + 2 + ... + n。
#include
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
cout << sum << endl;
return 0;
}
1. 普通运行
输入:
5
输出:
15
i 和 sum 分别是多少?
2. 设置断点
建议把断点设置在这一行:
sum += i;
这样每次循环执行到累加语句时,程序都会暂停,方便观察 i 和 sum。
3. 观察变量变化
输入 5 时,变量变化大致如下:
| 循环轮次 | i | 执行 sum += i 后的 sum |
|---|---|---|
| 第 1 次 | 1 | 1 |
| 第 2 次 | 2 | 3 |
| 第 3 次 | 3 | 6 |
| 第 4 次 | 4 | 10 |
| 第 5 次 | 5 | 15 |
七、Step Over 和 Continue 的区别
| 按钮 | 作用 | 课堂解释 |
|---|---|---|
| Step Over | 执行当前这一行,然后停到下一行 | 一步一步走 |
| Continue | 从当前位置继续执行,直到遇到下一个断点、程序结束、程序出错或等待输入 | 一路跑,直到遇到下一个红灯 |
八、变量是不是都会显示?
不是所有变量都会显示。调试时通常只能看到当前程序暂停位置能够访问到的变量。
例如:
#include
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
for (int i = 1; i <= n; i++) {
int temp = i * 2;
sum += i;
}
cout << sum << endl;
return 0;
}
- 程序停在
cin >> n;附近时,sum可能还没有定义或还没有初始化。 - 程序停在循环内部时,通常可以看到
n、sum、i、temp。 - 程序出了循环以后,
i和temp通常就看不到了,因为它们的作用域已经结束。
九、用 Debug 找循环边界错误
把正确程序中的循环条件故意改错:
for (int i = 1; i < n; i++) {
sum += i;
}
输入:
5
错误输出:
10
原因是 i < n 只会让 i 到 4,不会加到 5。
i 的变化,发现 i 没有到 5,最终定位到循环条件错误。
十、第二个演示:数组中找最大值
下面程序用于找到数组中的最大值:
#include
using namespace std;
int main() {
int a[5] = {3, 1, 4, 1, 5};
int maxx = a[0];
for (int i = 1; i < 5; i++) {
if (a[i] > maxx) {
maxx = a[i];
}
}
cout << maxx << endl;
return 0;
}
建议断点打在:
if (a[i] > maxx) {
重点观察:
i当前是多少a[i]当前是多少maxx是否需要更新
| i | a[i] | 当前 maxx | 是否更新 |
|---|---|---|---|
| 1 | 1 | 3 | 不更新 |
| 2 | 4 | 3 | 更新为 4 |
| 3 | 1 | 4 | 不更新 |
| 4 | 5 | 4 | 更新为 5 |
十一、常见错误与调试思路
| 常见错误 | 表现 | 调试时重点观察 |
|---|---|---|
| 循环少执行一次 | 结果偏小或漏掉最后一个元素 | i 是否到达目标值 |
| 循环多执行一次 | 数组越界或结果异常 | i 是否超过合法范围 |
| 变量忘记初始化 | 结果随机、每次可能不一样 | sum、cnt、maxx 的初始值 |
| 数组下标越界 | 程序崩溃或结果奇怪 | 数组下标是否在 0 到 n-1 之间 |
| if 条件写反 | 更新逻辑错误 | 条件成立时,程序是否进入了正确分支 |
十二、课堂练习
练习 1:找出循环边界错误
下面程序想计算 1 + 2 + ... + n,但结果错误。请使用 Debug 找出原因。
#include
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
for (int i = 1; i < n; i++) {
sum += i;
}
cout << sum << endl;
return 0;
}
提示:输入 5,观察 i 有没有到达 5。
练习 2:找出最大值程序的问题
下面程序想找到最大值,但当数组全是负数时会出错。请使用 Debug 观察 maxx 的变化。
#include
using namespace std;
int main() {
int a[5] = {-3, -8, -2, -6, -5};
int maxx = 0;
for (int i = 0; i < 5; i++) {
if (a[i] > maxx) {
maxx = a[i];
}
}
cout << maxx << endl;
return 0;
}
思考:为什么 maxx 一开始不能随便设成 0?应该如何修改?
练习 3:观察数组下标
下面程序存在数组下标问题。请使用 Debug 观察 i 和 a[i]。
#include
using namespace std;
int main() {
int a[5];
for (int i = 1; i <= 5; i++) {
cin >> a[i];
}
for (int i = 1; i <= 5; i++) {
cout << a[i] << " ";
}
return 0;
}
提示:C++ 中长度为 5 的数组,合法下标是 0, 1, 2, 3, 4。
十三、本节课总结
- 断点是程序的暂停点,不是程序的起点。
- 程序会从
main开始运行,到断点所在行暂停。 - Step Over 是一步一步执行。
- Continue 是继续运行到下一个断点、程序结束、程序出错或等待输入。
- 调试时显示的变量,是当前作用域中已经存在、可以访问的变量。
- Debug 的核心不是记按钮,而是观察变量变化,找到错误发生的位置。
十四、给学生的一句话
建议本节课安排在学生学完循环和一维数组之后,作为"程序调试与错误定位"专题讲解。后续在函数、递归、搜索、动态规划等内容中继续穿插使用 Debug。
