文章目录
- [1. 什么是bug?](#1. 什么是bug?)
- [2. 什么是调试(debug)?](#2. 什么是调试(debug)?)
- [3. Debug 和 Release](#3. Debug 和 Release)
- [4. VS调试快捷键](#4. VS调试快捷键)
-
- [4.1 环境准备](#4.1 环境准备)
- [4.2 核心调试快捷键](#4.2 核心调试快捷键)
- [5. 监视和内存观察](#5. 监视和内存观察)
-
- [5.1 监视](#5.1 监视)
- [5.2 内存窗口](#5.2 内存窗口)
- [6. 调试举例1:阶乘求和问题](#6. 调试举例1:阶乘求和问题)
- [7. 调试举例2:数组越界导致死循环](#7. 调试举例2:数组越界导致死循环)
- [8. 调试举例3:数组传参的调试](#8. 调试举例3:数组传参的调试)
- [9. 编程常见错误归类](#9. 编程常见错误归类)
-
- [9.1 编译型错误](#9.1 编译型错误)
- [9.2 链接型错误](#9.2 链接型错误)
- [9.3 逻辑型错误](#9.3 逻辑型错误)
1. 什么是bug?
bug本意是"昆虫"或"虫子",现在一般是指在电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题,简称程序漏洞。
"Bug"的创始人格蕾丝·赫柏(Grace Murray Hopper),她是一位为美国海军工作的电脑专家。1947年9月9日,格蕾丝·赫柏对Harvard Mark II设置好17000个继电器进行编程后,技术人员正在进行整机运行时,它突然停止了工作。于是他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有一只飞蛾,这显然是由于飞蛾受光和热的吸引,飞到了触点上,然后被高电压击死。所以在报告中,赫柏用胶条贴上飞蛾,并把"bug"来表示"一个在电脑程序里的错误","Bug"这个说法一直沿用到今天。

2. 什么是调试(debug)?
当我们发现程序中存在问题时,下一步就是找到问题并修复问题。
这个找问题的过程称为调试 ,英文叫debug(消灭bug)。
调试一个程序,首先要承认出现了问题,然后通过各种手段定位问题的位置(可能是逐过程调试,也可能是隔离和屏蔽代码的方式),找到问题所在后确定错误产生的原因,再修复代码并重新测试。
3. Debug 和 Release

在VS上编写代码时,会看到有debug和release两个选项,二者区别如下:
| 特性 | Debug(调试版本) | Release(发布版本) |
|---|---|---|
| 核心用途 | 程序员调试程序 | 交付用户使用 |
| 调试信息 | 包含完整调试信息 | 无调试信息 |
| 代码优化 | 不做任何优化 | 进行多种优化(代码大小、运行速度最优) |
| 文件大小 | 较大 | 较小 |
程序员开发阶段需将VS设置为Debug模式,便于调试;程序测试通过后切换为Release模式,生成用户使用的最终版本。
4. VS调试快捷键
4.1 环境准备
首先需将VS环境设置为Debug模式,如图:

4.2 核心调试快捷键
| 快捷键 | 功能说明 |
|---|---|
| F9 | 创建断点和取消断点;右击断点可以设置条件断点(满足指定条件才触发) |
| F5 | 启动调试,直接跳到下一个断点(需配合F9使用) |
| F10 | 逐过程调试:执行一个过程(函数调用或单条语句),不进入函数内部 |
| F11 | 逐语句调试:执行单条语句,可进入函数内部观察细节(函数调用处需用F11) |
| CTRL + F5 | 开始执行不调试:直接运行程序,无需进入调试模式 |
更多VS快捷键参考:http://blog.csdn.net/mrlisky/article/details/72622009
5. 监视和内存观察
调试过程中,可通过监视窗口 和内存窗口 观察变量的值及存储情况,前提是已启动调试(F5、F10或F11进入调试状态)。
示例代码:
c
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int num = 100;
char c = 'w';
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = i;
}
return 0;
}
5.1 监视
菜单栏【调试】->【窗口】->【监视】,打开任意一个监视窗口(监视1~4均可)。

在监视窗口的"名称"列输入要观察的变量名、数组名等,即可实时查看其值。

5.2 内存窗口
如果监视窗口看的不够仔细,也可以观察变量在内存中的存储情况。
菜单栏【调试】->【窗口】->【内存】,打开任意一个内存窗口(内存1~4均可)。

在内存窗口中观察数据:

在内存窗口的"地址"栏输入变量地址(如&arr、&num),即可查看该地址处的内存数据(以16进制形式展示)。

除此之外,调试时还可打开自动窗口、局部变量、反汇编、寄存器等窗口。
6. 调试举例1:阶乘求和问题
求1!+2!+3!+...+10!的和,以下代码运行结果错误,通过调试定位问题:
c
#include <stdio.h>
int main()
{
int n = 0;
int i = 1;
int ret = 1;
int sum = 0;
for(n = 1; n <= 10; n++)
{
for(i = 1; i <= n; i++)
{
ret *= i;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
调试思路
- 设断点:在
sum += ret;处设置断点(F9); - 启动调试(F5),通过监视窗口观察
ret和sum的值; - 发现
ret未在每次计算新阶乘前重置为1,导致累加错误; - 修复方案:在内部循环前添加
ret = 1;。
7. 调试举例2:数组越界导致死循环
在VS2022、X86、Debug环境下,以下代码执行后死循环,调试分析原因:
c
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
调试分析
- 内存布局:栈区内存从高地址向低地址分配,变量
i的地址高于arr数组的地址; - 数组越界:当
i=12时,arr[12]越界访问,恰好覆盖变量i的内存; - 死循环原因:
arr[12] = 0导致i被错误地重置为0,循环条件i <= 12永远成立。

- 栈区内存分配顺序与编译器、系统位数(X86/X64)、编译模式(Debug/Release)相关;
- X64或Release模式下,内存布局可能相反,该代码可能不会死循环。
8. 调试举例3:数组传参的调试
c
void test1(int d[])
{
}
int main()
{
int data[10] = {1,3,5,7,9};
test1(data);
return 0;
}
c
void test2(int a[3][5])
{
}
int main()
{
int arr[3][5] = {1,2,3,4,5 ,2,3,4,5,6 ,3,4,5,6,7};
test2(arr);
return 0;
}
调试思路
- 在数组传参的函数内部打断点,快速跳转到函数
- 调试进入函数,在监视窗口观察数组的内容:
- 一维数组:在监视窗口输入
数组名,元素个数(如d,10) - 二维数组:在监视窗口输入
数组名,行数(如a,3) - 不加元素个数/行数时,调试器无法完整展示数组内容,只能看到首元素或起始地址,如图:
- 一维数组:在监视窗口输入

调试核心原则:明确代码预期执行流程,通过调试验证实际执行是否与预期一致,根据差异点定位问题。
9. 编程常见错误归类
9.1 编译型错误
- 本质:语法错误
- 常见原因:缺少分号、括号不匹配、关键字拼写错误
- 排查方法:查看错误列表的提示信息,双击错误可跳转到错误位置或附近

9.2 链接型错误
- 本质:标识符未定义或引用失败
- 常见原因:标识符不存在、标识符拼写错误、头文件未包含、引用的库不存在

9.3 逻辑型错误
- 本质:代码逻辑错误(编译和链接均无错误,但出现运行时崩溃、结果异常等问题)
- 常见原因:数组越界、空指针访问、变量未初始化
- 排查方法:必须通过调试逐步定位问题,观察程序执行过程中的变量值、内存状态等