调试技术
调试技术
文章目录
1调试技术(1) – 编译错误与运行错误
编译错误与运行错误
新手会问的问题:
为什么编译都编译成功了,运行结果还有错?
编译错误:编译器提示的错误
编译器只帮我们检查语法错误(低级错 误),语法有错,那就不会生成exe程序
运行错误:程序运行的结果和预期的不一致 说明你的程序写的不对,需要改
编译错误
按F7生成,或Ctrl+Alt+F7重新生成
观察输出窗口,如果有错误,双击该行错误提示,
定位到哪行有语法错误
注:
(1)当有多条错误时,只看第1条错误 因为后面的一堆错误很可能是第1条引起的连串反 应
(2)如果错误匪夷所思,可以“重新生成解决方 案”,检查错误还在不在(链接)(正在链接…)
运行错误
解决运行错误,是程序员的主要工作之一。
运行时有错误,说明你的程序写的有问题。程 序员把问题/缺陷称为bug,把解决问题的过程 称为debug。
运行错误
举例:
对数组元素求和。
2调试技术(2) – 描述错误
何为错误?
(注:本章讲的错误都是“运行时错误”)
什么叫错误? 程序的运行结果不符合你的预期,就是错误。
简单地说: 输出结果和你想的不一样。。。
接下来的问题便是:你的预期是怎样的?程序 应该怎样输出?
精确地描述错误
一个错误的描述至少分为3个要素:
(1)前提条件
在什么条件下,错误必然会发生。
(2)预期结果
在这种条件下,你期望程序应该输出什么样的 结果?
(3)实际结果
程序实现的输出结果是怎么样的?
例1
需求:令用户输入两个数,程序输出两数之和。
例1
描述错误
(1)前提条件 输入1.1,2.2
(2)预期结果 输出3.3
(3)实际结果 输出值不是3.3
问题的复现
研发人员会根据问题的描述,来定位来解决问 题。
如果每次输入相同时,都能够重现相同的问题。 那么此问题是可以复现的。
如果输入相同,但问题不重现,将称问题是不 可复现的。
一般情况下,能复现的问题都是可以解决的。
小结
学会用三要素法来描述你遇到的错误。
代码 + 错误描述
3调试技术(3) – 定位错误
定位问题 定位问题:找到出问题的位置。
简单地说:找出来是哪一行写错了。。。
注:通常,把错误称为bug(臭虫),而找错 误的过程称为debug(调试)
单步调试的原理
我们即将使用的调试技术叫“单步调试”
其原理由以下2点构成:
(1) 程序是有很多步组成的
(2) 如果每一步都正确,则最终结果正确。
单步调试,就是走一步看一下结果,边走边观 察,直到发现有一步出错了。
单步调试的原理 怎么判断一步是正确还是错误?
每一步都应该有一个预期结果(观察变量和内 存的值),如果不合乎预期,就是这一步错误 了。
int a=0;
int b=1;
a += 10;
b -= 5;
int c = a * b;
printf("%d \n", c);
断点与单步调试
断点:break point (F9切换)
当程序运行到此行时暂停,进入单步模式。
单步:step over (F10)
运行程序,往下走一行。
继续运行: continue (F5)
继续运行程序,直到下一个断点,或程序结 束
例1
int a=0;
int b=1;
a += 10;
b -= 5;
int c = a * b;
printf("%d \n", c);
在调试窗口中观察每一步的结果
例1
- 加断点 2. 启动调试 3. 单步 在调试窗口中观察每一步的结果
例2
需求:令用户输入两个数,程序输出两数之和
(问题每次都能重现)
4调试技术(4) – 单步调试技术
单步调试技术
- 断点/单步/继续
- 调试窗口(观察局部变量、全局变量、内 存窗口)
- 修改变量/内存的值
- step into按层次来调试
按功能模块 断点/单步/继续
灵活使用这三者:
在合适的位置设置断点,隔离问题
F5继续:一次运行多行
5调试技术(5) – 观察变量的值
变量与内存
变量对于着一块内存,变量的值就是内存的值。
变量的大小就是它在内存中占据的大小
比如,sizeof(int)是4,表示占4字节的内存
变量与内存
char, short, int, double, float
数组
字符数组
结构体
指针
6调试技术(6) – 单元测试
单元测试
单元测试, Unit Test
将一个大的程序划分成若干单元进行测试
当代码越来越多时,将代码封装成多个函数, 把每个函数作为一个单元(unit)。
理论基础:
如果每个单元的功能都是正确的,则各个单元 联在一起也是正确的。
单元测试
举例:输入一个十六进制字符串,将该字符串 转成整数。
例如:输入 “A2”,则得到的整数为0xA2
// 将十六进制字符转成整数
unsigned char Hex2Int(char ch);
// 将十六进制字符构成的串转成整数
unsigned int Convert(const char* hex, int len);
测试用例
如何确定一个单元(函数)的功能是否正确呢?
答案:充分地测试
构造完整的测试用例(TestCase),确定函数在 每种情况(case)下都表现正常。
测试用例
如何确定一个单元(函数)的功能是否正确呢?
答案:充分地测试
构造完整的测试用例(TestCase),确定函数在 每种情况(case)下都表现正常。
比如,对于Hex2Int函数,让测试用例尽量覆 盖所有的情形
注意
(1)一个用例通过了,不代表函数就写对了。要 测试更多的用例。
(2)每次修改后,把单元测试用例重新全测一遍, 以免你的改动产生新的问题。
(3)设置先决条件,确定测试顺序
(4)当单元测试完成之后,我们就相信这个单元
(5)先单元测试,再集成测试(联调)
首先先决条件,然后单元测试,最后集成测试
7调试技术(7) - 程序崩溃的调试方法
程序崩溃
C/C++允许直接操作内存:
- 优点:灵活、高效
- 缺点:复杂、程序会崩溃
程序崩溃的调试方法及原因分类.pdf
8调试技术(8) - 程序崩溃的原因分类
程序崩溃的原因分类
- 读取未赋值的变量 一个变量未初化、未赋值,就读取它的值。 ( 这属于逻辑问题,往往是粗心大意的导致的 )
- 函数栈溢出
(1)定义了一个体积太大的局部变量
(2)函数嵌套调用,层次过深(如无穷递归) - 数组越界访问 访问数组元素时,下标越界
- 指针的目标对象不可用
(1)空指针
(2)野指针
- 指针未赋值
- free/delete释放了的对象
- 不恰当的指针强制转换
1,根据错误提示猜原因 2,定位调试出错位置