调试技术

调试技术


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

  1. 加断点 2. 启动调试 3. 单步 在调试窗口中观察每一步的结果

例2
需求:令用户输入两个数,程序输出两数之和

(问题每次都能重现)

4调试技术(4) – 单步调试技术

单步调试技术

  1. 断点/单步/继续
  2. 调试窗口(观察局部变量、全局变量、内 存窗口)
  3. 修改变量/内存的值
  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)函数嵌套调用,层次过深(如无穷递归)
  3. 数组越界访问 访问数组元素时,下标越界
  4. 指针的目标对象不可用
    (1)空指针
    (2)野指针
  • 指针未赋值
  • free/delete释放了的对象
  • 不恰当的指针强制转换

1,根据错误提示猜原因 2,定位调试出错位置
调试技术

学习资源 《C语言/C++学习指南》补充篇(从入门到精通)》