第8节-实用调试技巧

目录

1. 什么是bug

2. 调试是什么,有多重要

2.1. 调试是什么

2.2.调试的基本步骤

2.3. Debug和Release的介绍

3. Windows环境调试介绍

3.1.学会快捷键

 3.1.1.切换断点、新建断点快捷键 F9 / 开始调试快捷键F5

3.1.2.逐过程 F10 / 逐语句 F11

3.1.3.开始执行(不调试) ctrl+F5

3.1.4.vs编译器中更多的快捷键

3.2.调试的时候查看程序当前信息

 3.2.1.自动窗口

3.2.2.局部变量

3.2.3.监视

3.2.4.内存

3.2.5.反汇编

3.2.6.寄存器

3.2.7.调用堆栈

4.调试的实例

5.如何写出好(易于调试)的代码

5.1.优秀的代码

5.2.示例(※)

6.编程常见的错误

6.1.编译型错误

6.2.链接型错误

6.3.运行时错误


1. 什么是bug

第一次被发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。  


2. 调试是什么,有多重要

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

一名优秀的程序员是一名出色的侦探。

每一次调试都是尝试破案的过程

2.1. 调试是什么

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

2.2.调试的基本步骤

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

第一步:发现程序错误的存在

1.程序员->可以发现错误

2.测试(开发)工程师->也可以发现程序的错误  报bug

----------------程序发布----------------

3.用户->也可以发现(可能就有风险了!)

第二步:以隔离、消除等方式对错误进行定位

第三步:确定错误产生的原因

第四步:提出纠正错误的解决办法

第五步:对程序错误予以改正,重新测试

2.3. DebugRelease的介绍

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。(bebug版本可以调试)
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。(Release版本是无法调试的)

 注:

1.当程序选择debug或release时ctrl+F5进行编译,这时文件夹里就会有debug或release文件夹,文件夹里放的就是debug调试版本或release发布版本。

编译前文件夹:

编译:

 

 编译后文件夹:

 2.可以看出debug调式版本里的.exe运行文件要比release发布版本里的.exe运行文件大很多(因为debug调式版本里包含调试信息

 


3. Windows环境调试介绍

3.1.学会快捷键

 3.1.1.切换断点、新建断点快捷键 F9 / 开始调试快捷键F5

F9 切换断点、新建断点

F5 开始调试(通常使用F5跳到想要的断点处)

断点:程序执行到断点处就会主动停下来

1.F9和F5是配合使用的:当我们想从某一行代码开始调试时(前面的代码自动运行完),在该行代码设置断点,然后F5进行调试,然后再按F10往下调试即可

2.F5是向后执行代码,到下一个逻辑上的断点,如下图,断点设在循环内,每按一次F5代码 ,循环都会执行一次并且执行到这个断点处停止,因此我们可以直观的看到按一次F5,右边监视窗口i的值增加1

3.1.2.逐过程 F10 / 逐语句 F11

F10 逐过程(每按一次,执行一条语句)(粗劣进行每一行执行)

F11 逐语句 (每按一次,执行一条语句,遇见函数时,可进入函数内部执行)(若可细分,进行精细执行)

3.1.3.开始执行(不调试) ctrl+F5

ctrl+F5 开始执行(不调试)

3.1.4.vs编译器中更多的快捷键

(40条消息) VS中常用的快捷键_MrLisky的博客-CSDN博客_vs快捷键

3.2.调试的时候查看程序当前信息

 3.2.1.自动窗口

vs编译器自动获取程序上下文环境中的某些变量,进行展示

自动展示某些变量自动隐藏展示的变量,不方便,一般不进行使用

3.2.2.局部变量

会把程序执行过程中上下文环境中的局部变量进行列举展示

3.2.3.监视

可以把想要观察的数据自己加进去

3.2.4.内存

输入地址可以观察到内存地址中存的数据值

3.2.5.反汇编

可以观察到程序在执行过程中,其汇编代码的变化

(也可以在代码界面点击鼠标右键,点击反汇编)

3.2.6.寄存器

可以观察到各类寄存器的变化状态(在监视窗口输入想要观察的寄存器名字也可以观察到)

3.2.7.调用堆栈

可以观察到函数调用的逻辑


4.调试的实例

实例1:

实现代码:求 1 +2 +3 ...+ n! ;不考虑溢出。
调试下面代码,发现代码中的bug
代码:
#include<stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	int i = 0;
	int ret = 1;
	int j = 0;
	int sum = 0;
	for (j=1; j<=n; j++)
	{
		for (i = 1; i <= j; i++)
		{
			ret *= i;
		}
		sum += ret;
	}
	
	printf("%d\n", sum);

	return 0;
}

调试的过程:

1. 我们观察到当j=3时算3!应该是6,而代码算出来的是ret=12,此时代码已经走过去了,我们想退回去从j=3重新开始进行观察。

2.我们从j=3重新开始进行观察,我们在i的for循环处设置断点,鼠标放在断点红圈处单机鼠标右键,选择条件,条件改成j==3,关闭后,断点变成了条件断点,此时按F5调试可以观察到是从j=3开始的。

 3.此时我们可以看到当j=3算3的阶乘时,ret的初始值并不是1,而是2。此时我们便发现了问题 

 4.修改代码,写出正确代码如下

int main()
{
	int n = 0;
	scanf("%d", &n);
	int i = 0;
	int ret = 1;
	int j = 0;
	int sum = 0;
	for (j=1; j<=n; j++)
	{
		ret = 1;
		for (i = 1; i <= j; i++)
		{
			ret *= i;
		}
		sum += ret;
	}
	
	printf("%d\n", sum);

	return 0;
}

实例2:

研究下面程序死循环的原因
代码:

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

调试的过程:

1.我们看到直到i=11,即使越界访问,代码都会对数据进行更改变成0

 2.而当i=12,对arr[12]进行更改时,arr[12]可以变成0,而当arr[12]变成0时,i的值也跟着变成0(细心的话我们可以看到前面arr[12]里面的值总是会与i同时变化),此时我们要对其进行分析。

 3.我们对其进行分析,我们发现arr[12]里面的值总是和i同时变化,那么arr[12]里面的值是否就是i的值呢?也就是arr[12]的地址是否与i的地址相同呢,我们可以进行观察,下图中我们看到arr[12]的地址与i的地址完全相同,因此我们可以得出当i=12时,arr[i]=0其实就是又将i赋值成0,循环结束条件永远无法实现,因此死循环。

 注:

1.i和arr是局部变量,局部变量是放在栈区上的,栈区上内存的使用习惯是:先使用高地址处的空间,再使用低地址处的空间

2.数组随着下标的增长,地址是由低到高变化的

3.如果i和arr之间的空间适合的话,就有可能使用的arr数组向后越界就访问到了i,造成循环变量i的改变,最终死循环

4.编译器不一样,数组后面空余的空间数也不一样,所以该代码是严格依赖环境的

vs编译器 空2个空间

vc6.0 不空空间

gcc 空1个空间

 5.我们发现当我们改成release发布版本时再进行编译,该代码便不会死循环了,如下图所示,这是因为在release版本中将代码中的i=0放在了代码arr[10]={1,2,3,4,5,6,7,8,9,10}的后面(也就是将i的地址放在了arr地址的前面) 


5.如何写出好(易于调试)的代码

5.1.优秀的代码

1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全
常见的coding技巧:
1. 使用assert
2. 尽量使用const
3. 养成良好的编码风格
4. 添加必要的注释
5. 避免编码的陷阱。

5.2.示例(※)

1.模拟实现库函数:strcpy

strcpy函数介绍:

 由下代码知,strcpy会将字符串中的'\0'也拷贝过去。

 代码1:(该代码有bug) 

代码2:

 代码3:

代码4:(模拟strcpy函数返回值功能)

代码5:(完全模拟strcpy函数功能,加了const)

注:

1.代码1不好,因为如果传的指针是空指针的话,对空指针解引用程序就会有问题

2.assert是断言的意思,assert函数若括号里的内容为假,就会报错,如下图

 3.assert函数需要头文件assert.h

4. 代码3,'\0'的ASCII码值为0因此可以用'\0'跳出循环

5.代码4,strcpy函数其实是有返回值的,返回的是目标字符串的地址

 6.strcpy函数传参时形参的源字符串前有const修饰,以防下面while循环处将源字符串地址赋值给目标字符串地址时两地址写反(加了const就使得此处写反,系统无法运行),此处const是希望限制*p不能被修改,所以应该放在*的前面。

7.const修饰变量,该变量变成常变量,常变量无法被修改(但用指针变量对其进行修改时,该变量会被改变)

   const修饰指针变量,当const放在*的左边(const int * p=&n 或 int const * p),修饰的是指针指向的内容,表示指针指向的内容不能通过指针来改变(不能对*p也就是p的解引用进行改变,可以对指针变量p进行改变,也就是p可以取其他地址)。当const放在*的右边(int * const p=&n),修饰的是指针变量本身,表示指针变量的内容不能被改变(可以对*p也就是p的解引用进行改变,不能对指针变量p进行改变,也就是p不能取其他地址)。当*的左右两边都有const,那么既限制了*p又限制了p。

拓展思考:const修饰二级级指针(有三种情况)

2.模拟实现库函数:strlen

代码:


6.编程常见的错误

6.1.编译型错误

编译错误:都是语法问题引起的
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。
相对来说简单

6.2.链接型错误

一般是标识符名不存在或者拼写错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。
双击错误提示信息没有反应,需要自己去程序中找错误的标识符
搜索框快捷键:ctrl+F 
跳转到哪一行快捷键:ctrl+G 

 

6.3.运行时错误

借助调试,逐步定位问题。
最难搞
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随风张幔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值