[Visual Studio]----调试技巧

目录

1. 什么是bug?

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

2.1 调试是什么? 

2.2 调试的基本步骤

2.3 Debug和Release的介绍

3. Windows环境调试介绍

3.1 调试环境的准备

3.2 调试快捷键

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

3.3.1 查看临时变量的值

3.3.2 查看内存信息

3.3.3 查看调用堆栈

3.3.4 查看汇编信息

4. 调试实例

4.1 实例1

4.2 实例2

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

5.1 优秀的代码:

5.2 assert的作用

5.3 const的作用

5.4 优秀代码示范

5.4.1 模拟实现strcpy

5.4.2 模拟实现strlen


1. 什么是bug?

下面这张图片是历史上的第一个bug:
 

bug的本意是虫子的意思,历史上第一台计算机出现问题的原因是计算机中出现了一个虫子,后来我们引申了一下,把计算机中出现的漏洞或者造成程序异常的情况我们叫做bug

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

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

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


顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
 

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

调试不是一眼就能看出来的,需要我们慢慢用心去研究 

2.1 调试是什么? 

我们了解了bug是什么,那么调试又是什么呢 ?

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


2.2 调试的基本步骤
 

1.发现程序错误的存在


2.以隔离、消除等方式对错误进行定位


3.确定错误产生的原因


4.提出纠正错误的解决办法


5.对程序错误予以改正,重新测试

 步骤看似简单,实则需要用心体会

2.3 Debug和Release的介绍
 

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

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

 Debug版本可以对程序进行调试,用来发现程序的不足之处

Release版本是发布版本,不能对程序进行调试,而是直接执行程序

这两个版本可以在程序文件下找到,可以发现,Debug版本的程序内存比Release版本的大很多

3. Windows环境调试介绍
 

3.1 调试环境的准备
 

在环境中选择 debug 选项,才能使代码正常调试。

3.2 调试快捷键

在调试中有许多可供选择的调试方式:

F5:

启动调试

经常用来直接跳到下一个断点处(配合断点使用) 

F9:
创建断点和取消断点


断点的重要作用,可以在程序的任意位置设置断点


这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去


断点使用:

我们在想要停止的语句上打断点,再按F5,程序会直接到断点处停止

也可以在循环中使用断点,并给断点附上条件,使断点到条件处停止

例如我们把断点的条件定在i == 5 时,当按F5执行程序时,程序会到断点处(i == 5 时)停止

 


F10:

逐过程

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

F11:
逐语句 ( 比逐过程更加细致 )

就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最
长用的)

CTRL + F5

开始执行不调试

如果你想让程序直接运行起来而不调试就可以直接使用

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

3.3.1 查看临时变量的值
 

在调试开始之后,用于观察变量的值
 

 可以通过调试中的监视功能查看变量值

3.3.2 查看内存信息
 

在调试开始之后,用于观察内存信息
 

  可以通过调试中的内存功能查看内存所储存的信息

3.3.3 查看调用堆栈
 

通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置

栈:是一种数据结构,其特点是先进后出,这里的调用函数方式就是一种栈的存储模式,使我们清楚看见函数是被哪些函数调用的

3.3.4 查看汇编信息
 

 通过查看汇编信息,可以深层理解代码结构

第一种方式:

F10调试起来后,鼠标右键选择反汇编

 第二种方式:
F10调试起来后,通过调试窗口进行选择:

总结:多多动手,尝试调试,才能有进步

4. 调试实例

4.1 实例1

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

给出如下有问题的代码:

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

当我们输入3时,3!应该 = 1 + 2 + 6 = 9

 

这时候我们输入3,期待输出9,但实际输出的是15,很显然此时Bug已经出现,这时候就需要我们进行调试

通过调试的监视功能,我们发现当我们调试到计算3的阶乘时,我们发现此时ret的值为12,也就是说程序计算的3的阶乘值是12,这时候错误就已经显现出来了。

原因是我们在计算完2的阶乘之后,ret的值没有重新赋值成1,导致3的阶乘是在2的阶乘上计算出来的,所以会对3的阶乘计算产生错误影响

解决方案:

此时只需要把计算完每次阶乘的时候,把ret赋值成1,错误就会迎面而解

 改进后的代码:

#include<stdio.h>
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;//每计算完一个数的阶乘后,重置ret的值
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

4.2 实例2

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

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

我们从表面上看,数组的元素个数为10,,而在for循环中,共循环了12次访问数组内部元素,此时会对其他地方地址产生越界访问,程序因此崩溃

而当我们实际运行起来会发现,程序并没有崩溃,而是死循环打印


当F10调试起来,通过观察当 a[11] = 0 时,i的值还是这个,而当进行arr[12] = 0操作时,此时i的值变为0

arr[11] = 0;

arr[12] = 0; 

而当我们查看i的地址和arr[12]的地址时,我们会发现它们两个的地址相同,所以显而易见,当我们修改arr[12]的值时,会对内存进行非法访问,而非法访问的地址正好也是i的地址,所以会造成i又从0开始,导致程序死循环

(虽然arr[12]不在我们申请的内存中,但它表示arr[9]后面的第3个地址,我们在对arr[12]进行访问,会访问到其他未申请的地址)

总结原因:

i和arr都是局部变量,都存储在栈区中。在栈区中创建局部变量是先使用高地址处的空间 再使用低地址处的空间,所以i的地址在arr的后面,又因为for循环导致数组越界访问,正好arr[12]的地址是i的地址,所以当修改arr[12]的值时,i的值也被修改成0,循环又从头开始,因此导致程序死循环。

i和arr[12]的地址一样


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

5.1 优秀的代码:
 

1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全

常见的coding技巧:


1. 使用assert
2. 尽量使用const
3. 养成良好的编码风格
4. 添加必要的注释
5. 避免编码的陷阱

5.2 assert的作用

首先介绍assert函数(头文件 assert.h): 

 assert( ) ,当()内为假时,程序会报错

assert相当于if判断更加严厉

  

5.3 const的作用

由const修饰的变量不能被更改 

 const修饰指针变量的时候

1. const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变但是指针变量本身的内容可变


2. const如果放在*的右边修饰的是指针变量本身保证了指针变量的内容不能修改但是指针指向的内容,可以通过指针改变

5.4 优秀代码示范

5.4.1 模拟实现strcpy

strcpy在拷贝字符串的时,会把原字符串中的'\0'也拷贝过去 

返回值为目标字符串的首地址

char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);//断言 引用头文件<assert.h>

	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

5.4.2 模拟实现strlen

int my_strlen(const char* str)
{
	assert(str);
	int count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}


 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Visual Studio是一款常用的集成开发环境,它提供了许多调试技巧和方法来帮助开发人员快速定位和解决问题。以下是一些常用的Visual Studio调试技巧: 1. 悬停鼠标查看表达式值:在调试过程中,你可以将鼠标悬停在代码中的表达式上,Visual Studio会显示该表达式的当前值,这对于快速检查变量的值非常有用。[1] 2. 使用Watch窗口:Watch窗口可以让你监视和跟踪变量的值。你可以在Watch窗口中添加你感兴趣的变量,并在调试过程中实时查看它们的值。[2] 3. 使用条件断点:条件断点允许你在满足特定条件时中断程序的执行。你可以在断点设置对话框中设置条件,当条件满足时,程序会在该断点处停止执行。这对于调试特定情况下的代码非常有用。 4. 使用调试输出:你可以在代码中插入调试输出语句,以便在调试过程中输出特定的信息。你可以使用`Debug.WriteLine`或`Trace.WriteLine`方法来输出信息,并在输出窗口中查看它们。 5. 使用调试工具:Visual Studio提供了许多强大的调试工具,如调试器窗口、内存窗口、线程窗口等。这些工具可以帮助你深入了解程序的执行过程,并找到潜在的问题。 6. 使用命令窗口:Visual Studio的命令窗口可以让你通过输入命令来自动化调试过程。你可以使用命令窗口执行一些简单的操作,如测试变量的值或执行特定的调试命令。[3] 这些是一些常用的Visual Studio调试技巧,它们可以帮助你更高效地进行调试工作,并节省大量的时间。希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值