6. 调试举例1
求1!+2!+3!+4!+...10!的和,请看下面的代码:
#include <stdio.h>
//写一个代码求n的阶乘
int main()
{
int n = 0;
scanf("%d", &n);
int i = 1;
int ret = 1;
for(i=1; i<=n; i++)
{
ret *= i;
}
printf("%d\n", ret);
return 0;
}
//如果n分别是1,2,3,4,5...10,求出每个数的阶乘,再求和就好了
//在上面的代码上改造
int main()
{
int n = 0;
int i = 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;
}
//运行结果应该是错的?
调试找一下问题。
7. 调试举例2
在VS2019、X86、Debug 的环境下,编译器不做任何优化的话,下面代码执行的结果是啥?
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
程序运行,死循环了,调试看看为什么?
调试可以上面程序的内存布局如下:
1. 栈区内存的使用习惯是从高地址向低地址使用的,所以变量i的地址是较大的。arr数组的地址整体是小于i的地址。
2. 数组在内存中的存放是:随着下标的增长,地址是由低到高变化的。所以根据代码,就能理解为什么是左边的代码布局了。如果是左边的内存布局,那随着数组下标的增长,往后越界就有可能覆盖到i,这样就可能造成死循环的。
这里肯定有同学有疑问:为什么i和arr数组之间恰好空出来2个整型的空间呢?这里确实是巧合,在不同的编译器下可能中间的空出的空间大小是不一样的,代码中这些变量内存的分配和地址分配是编译器指定的,所以的不同的编译器之间就有差异了。所以这个题目是和环境相关的。
从这个理解我们能够体会到调试的重要性,只有调试才能观察到程序内部执行的细节,就像医生给病人做B超,CT一样。
8. 调试举例2:扫雷
如果一个代码稍微复杂,那怎么调试呢?
这里我们就上手调试一下扫雷的代码。
演示:
• 在函数内部打断点,快速跳转到函数
• 在数组传参,调试进入函数,如何在监视窗口观察数组的内容: 数组名,n 的形式
一维数组通过形参关键数组内容
二维数组通过形参关键数组内容
调试过程中,要做到心中有数,也就是程序员自己心里要清晰的知道希望代码怎么执行,然后再去看代码有没有按照我们预定的路线在执行。
调试是需要反复去动手练习的,调试是可以增加程序员对代码的理解和掌控的,掌握了调试的能力,就能看到本质,就像能给程序做B超一样,对程序内部一览无余。
9. 编程常见错误归类
9.1 编译型错误
编译型错误一般都是语法错误,这类错误一般看错误信息就能找到一些蛛丝马迹的,双击错误信息也能初步的跳转到代码错误的地方。编译错误,随着语言的熟练掌握,会越来越少,也容易解决。
9.2 链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是因为
• 标识符名不存在
• 拼写错误
• 头文件没包含
• 引用的库不存在
9.3 运行时错误
运行时错误,是千变万化的,需要借助调试,逐步定位问题,调试解决的是运行时问题。