实用调试技巧(1)

  • 什么是bug?
  • 调试是什么?有多重要?
  • debug和release的介绍。
  • windows环境调试介绍。
  • 一些调试的实例。
  • 如何写出好(易于调试)的代码。
  • 编程常见的错误。

什么是Bug
我们在写代码的时候遇到的一些问题而导致程序出问题的就是Bug,世界上的第一个Bug是一支飞蛾,这就是Bug的由来,在早期的时候,机器突然坏了,工作人员进行检查,最后发现是一只飞蛾导致机器故障,所以也有了现在的Bug之称。

当天的工作人员将他贴在了笔记本上,这就是时间上的第一个Bug。
调试是什么
我们在初学的时候会遇到各种各样的问题,这个时候我们就需要进行调试,这样才能解决Bug,所以调试非常重要,一个出色的程序员必须会调试

所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。
顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。

但是我们在初学的时候总是盲目的调试,可能只是简单摁下键盘上的F10,调试一直到我们代码结束的时候,只是走了一遍流程,却不知道问题在哪里,这就是我们初学者经常出现的问题。
所以我们要拒绝迷信调试,要真正的找到问题所在,解决这个臭虫

2.1 调试是什么?
调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。

2.2 调试的基本步骤

  • 发现程序错误的存在
  • 以隔离、消除等方式对错误进行定位
  • 确定错误产生的原因
  • 提出纠正错误的解决办法
  • 对程序错误予以改正,重新测试

而我们写程序的时候出现Bug生活中一般有三种人,一是程序员自己,二是测试员,三是用户。
测试员就是相当于你写的代码经过relase之后到测试员那里,它进行测试。

3 Debug和Release的介绍

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

就是相当于我们平常在编译器上写的代码

Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

这个就是我们的代码经过优化后呈现给用户的

那我们用代码给大家演示一下他们有什么不一样的地方

#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = -1;
	}
	for (i = 0; i < 10; i++)
	{

		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述
上面就是我们在Debug和release下生成的可执行文件

实用调试技巧(1)_第1张图片
实用调试技巧(1)_第2张图片
很明显Release下的内存明显小了很多,说名它进行过优化,我们这是在X86平台下,就是32位机器下,当然我们改成X64也是一样的道理

实用调试技巧(1)_第3张图片

实用调试技巧(1)_第4张图片
3. Windows环境调试介绍
那我们在了解环境之前先要来了解一些东西,比如就是快捷键,何为快捷键,就是让你变得快捷起来。

实用调试技巧(1)_第5张图片
在我们的调试里头就有这些快捷键,来介绍几个常用的
CTRL + F5

这个键就是我们每次运行要得到我们的结果的时候用的,它的意思就是开始执行不调试,直接运行程序。

F11

这个就是一条一条语句往下执行,我们在调试过程中这是必然不可缺少的,它也可以进到我们的函数当中去。

F10

逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句

F10和F11其实差不多,就是后者更细致一点,作用都是差不多的。

F9

创建断点和取消断点断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。

F5

启动调试,经常用来直接跳到下一个断点处.

下面举例子给大家看看断点有多方便。

实用调试技巧(1)_第6张图片

#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = -1;
	}
	for (i = 0; i < 10; i++)
	{

		printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		printf("hehe\n");
	}
	return 0;
}

我们在上面设置了一个断点,并且条件是i==6,那让我们按下F5看看吧。
实用调试技巧(1)_第7张图片
上面我只按了一个F5就到了,可是如果我按F10,要经过一个半的for循环,有时候按的快的时候,一下子按过头了又要重新按(小编一开始就是这么蠢,大家学聪明点),可见断点和F5连用是这么方便。
实用调试技巧(1)_第8张图片
同时屏幕上也打印了6个-1
还有其他相当多的快捷键大家也可以去网上找找,增加对VS的理解

会快捷键之后我们还需要学习一些其他的,往下看
3.3 调试的时候查看程序当前信息
实用调试技巧(1)_第9张图片

在窗口里有各式各样的好东西,让我们来看看吧
当然我们这些窗口必须是要开始调试起来的时候才能看到,我一开始学的时候,不知道这个,废了好大劲才找到我原来都没有开始调试。
监视窗口
实用调试技巧(1)_第10张图片
任意选一个都可以,在这里我们就可以输入我们想要监视的,比如数组名,还有数组的元素,一些变量,我们都可以看到它时刻的值,当然大家也可以看到自动窗口和局部变量,这些也都是监视的,只不过它是自己生成的,虽然很方便但是它有时候会自己变值,总的来说还是不便于观察,我们在学得时候,小编建议大家还是自己尝试,这样才能提升自己的调试技巧。
内存窗口
这个也很重要,我们在栈上创建变量的时候,都是占用空间的,而我们会用相应的编号找到他们的位置,这也是让我们更好的观察他们,让我们更容易调试,找到Bug。

3.3 查看调用堆栈

调用堆栈主要是反映逻辑关系,比如我们过多的调用函数的时候不明白它的逻辑的时候可以使用

举个例子

#include
void test1()
{
	printf("hehe\n");
}
void test()
{
	test1();
}
int main()
{
	test();
	return 0;
}

实用调试技巧(1)_第11张图片
我们返回的时候是下面这样
实用调试技巧(1)_第12张图片
啥意思呢,就是我们之前讲过函数栈帧一样,栈是一步一步在顶上开辟,然后一步一步返回的。

3.4 查看汇编信息

实用调试技巧(1)_第13张图片
在函数栈帧讲过,建议大家去看一下
3.5 查看寄存器信息
实用调试技巧(1)_第14张图片
之前讲过esp和ebp还有edi这些,他们的作用都是一样,存储数据,其中ebp和esp有维护函数栈帧的作用。

接下来给两个例子给大家调试一下看,不过都是最基础的调试.

实例

实现代码:求 1!+2!+3! …+ n! ;不考虑溢出。

int main()
{
 int i = 0;
 int sum = 0;//保存最终结果
 int n = 0;
 int ret = 1;//保存n的阶乘
 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;
}

我们的代码主要是为了计算阶乘的大小,前几个阶乘相加计算出结果,但是我们上面的代码其实是有问题的,我们一步一步调试来看看,首先我们要写出我们每次心里想的结果去对比编译器中监视进行对比,找出问题。
实用调试技巧(1)_第15张图片
实用调试技巧(1)_第16张图片

调试之前可以像我一样把要观察的写出来

实用调试技巧(1)_第17张图片
j循环的for语句执行一次,因为i=1,算出1的阶乘,答案是对的。
我们看2的阶乘
实用调试技巧(1)_第18张图片

实用调试技巧(1)_第19张图片
其实我们在这里就能看出结果,原因是我们是在原来的基础性上阶乘,但是运气好,答案是对的,当我们继续往下走,就会看出端倪

实用调试技巧(1)_第20张图片
一看和我们预想的有问题,这时候就要警惕,如果我们第一次没看出来,但是结果不一样,这时候就要小心了,不过很显然,为什么造成这个原因,我们在调试过程一看就才可以看出来了,ret一直是在原来基础上改变,所以我们给他进入循环的时候变成1就可以了。

改正

#include

int main()
{
	int i = 0;
	int sum = 0;//保存最终结果
	int n = 0;
	int ret = 1;//保存n的阶乘
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		int j = 0;
		ret = 1;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

实例two

#include 
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;
}

想要解释这道题必须调试才能知道
给个前提,必须在VS这个编译器下,而且是X86,Debug下,别的一些编译器也行,但还是有点初入,这里我们不进行调试是做不出来,下面调试。

实用调试技巧(1)_第21张图片

当我们的i变成9的时候,刚刚把原始数组都赋值为0

实用调试技巧(1)_第22张图片
很神奇,我们数组下标到9就应该结束,但是现在竟然把arr[10]的内容赋值为0,我们语句越界访问了

实用调试技巧(1)_第23张图片
11也是,i还在变大
实用调试技巧(1)_第24张图片
arr[12]怎么放的是i的值,好奇怪,我们继续往下。
实用调试技巧(1)_第25张图片

i突然变成0了,arr[12]的内容也是0,说明赋值成功,但是i又变了。我们就要去怀疑他们是不是公用一个空间

实用调试技巧(1)_第26张图片
结果一看还真是,那就说明arr[12]和i用的是一个地址,后来i又变成0了,所以才会死循环,讲到这里大家肯定明白了把


实用调试技巧(1)_第27张图片
说面我们arr数组开辟的时候和i中间差两个整型,我们数组的下标是由低地址到高地址的

今天我们先讲一部分调试技巧,内容太多了,小编写不动了,谢谢大家!!!

你可能感兴趣的:(c语言,开发语言,c++)