程序错误(bug),是程序设计中的术语。是指在软件运行过程中,因为程序本身有错误而造成的功能不正常、死机、数据丢失等现象。
1947年,人们发现了第一个电脑上的bug。
当时电脑出现问题,整个团队都搞不清楚为什么电脑不能正常工作了。经过大家的深度挖掘,发现原来是一只飞蛾意外飞入了电脑内部而引发的故障。团队很快排除错误,在日志本中记录下这一事件。之后,人们渐渐开始使用“Bug”(愿意为“虫子”)来称呼计算机中的错误。
每一位优秀的程序员都是一名出色的侦探。
每一次调试都是尝试破案的过程。
调试(Debugging / Debug),是发现和减少计算机程序或者电子仪器设备中程序错误的一个过程。
Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优解,以便用户很好的使用。
二者的区别很大,体现在代码运行后的目录文件下。
#include
int main() {
char* p = "hello world.";
printf("%s\n", p);
return 0;
}
常用的几个快捷键:
F5:启动调试,经常原来直接跳到下一个断点处。
F9:创建断点和取消断点,可以在程序中任意位置设置断点。
F10:逐过程调试,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11:逐语句调试,每次都执行一条语句,如果遇到函数,可以使我们进入函数内部查看。(这是最常用的)
CTRL + F5:直接开始执行,跳过所有断点,不调试。
查看临时变量的值
在调试开始之后,用于观察变量的值。
可以在调试→窗口→监视→监视1中打开。
查看内存信息
在调试开始之后,用于观察内存信息
在调试→窗口→内存→内存1 打开
查看调用堆栈
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。和栈一样,先进后出。
在调试→窗口→调用堆栈 中打开。
查看汇编信息
开始调试之后,有两种方法转到汇编:
(1)第一种方式:右击鼠标,选择【转到反汇编】;
(2)第二种方式:在调试→窗口→反汇编;
查看寄存器信息
可以查看当前运行环境的寄存器的使用信息。
调试→窗口→寄存器
实现代码:求1! + 2! + 3! + … + n!;
int main() {
int i = 0;
int n = 0;
int sum = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++) {
int j = 0;
for (j = 1; j <= i; j++) {
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
此段代码,我们输入3,期待输出9,但是结果是15。
为什么?这个时候就是代码有问题,试着运用调试来解决。
1. 查看代码,给可能出现问题的地方打上断点
2. F5进入调试环境,并且调出监视窗口
3. 按F11,逐步查看代码,要心中有数知道该段代码执行结果,然后与监视窗口对比
我们发现 ret 的值,与预期不一样,3!应该是 6 ,而不是 12。
4. 再进行一次调试,重点查看 ret 的值
发现 ret 计算完还是上次循环的数据,没重置,所以我们加上代码重置试一下。
5. 修改代码,重新运行
OK~
int main() {
int i = 0;
int arr[10] = { 0 };
for (i = 0; i <= 12; i++) {
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
这段代码,很明显是数组越界访问了,但是为什么编译器没有报错,还无限循环输出 hehe 呢?
让我们调试查看一下。
调试到 i=10 还没有报错?
调试到 i=12 继续执行,i=0 了?
奇怪,这时候我们查看一下 arr[12] 的地址
发现 arr[12] 的地址和 i 的地址是一样的。
原来是这样!
数组循环到 i=12 的时候,发现数组 arr[12] 和 i 的地址一样。又因为循环体中 arr[i] = 0 这个代码,把 arr[12] 的数据改为 0,导致 i 的数据也改为 0,回去循环判断又处理。
这才是为什么越界还一直死循环!
然后我们把循环判断条件改小试一下,程序会正常判断到数组越界了。