学会VS调试技巧,学习工作无烦恼

学会VS调试技巧,学习工作无烦恼_第1张图片

先赞后看,不足指正!

这将对我有很大的帮助!

所属专栏:C语言知识

阿哇旭的主页:Awas-Home page


目录

引言

环境准备

1. 什么是bug?

2. 什么是调试(debug)?

3. Debug 与 Release

4. VS调试快捷键

5. 监视和内存观察 

5.1 监视

5.2 内存

6. 调试举例

6.1 举例一

6.2 举例二 

6.3 举例三

7. 编程常见错误归类

7.1 编译型错误

7.2 链接型错误

7.3 运行时错误

8. 结语


引言

        俗话说的好:“工欲善其事,必先利其器”。而我们的C语言学习也是如此,不仅要打牢基础,也要使用工具来辅助我们的学习,好比“君子生非异也,善假于物也”。

        那么,话不多说,我们一起来看看吧!


环境准备

        基于Visual Studio 2022,展开调试技巧的介绍。


1. 什么是bug?

        bug本意是“昆虫”或“虫子”,现在一般是指在电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题,简称程序漏洞。(具体介绍:第一个程序臭虫(Bug)的由来)


2. 什么是调试(debug)?

        在我的理解看来,debug的全称就是Destroy bug(消灭臭虫)。

        当我们运行程序并发现程序中存在的问题的时候,那么下一步就是找到问题,并修复问题。这个时候我们就需要调试(debug),简单来说就是发现问题,解决问题的过程。


3. Debug 与 Release

380aceed7a9d49fd86d6572b02760e9c.png        我们在VS编译器中可以看到 DebugRelease 这两个选项,它们分别称为 调试版本 发布版本 ,具体区别如下表所示:

选项 优点 缺点
Debug 包含调试信息,便于调试程序 不作任何优化,且文件较大
Release 进行了优化,代码大小和运行速度都是最优的,且文件较小 不包含调试信息,不能进行调试

          图例对比:Debug文件与Release文件

学会VS调试技巧,学习工作无烦恼_第2张图片

学会VS调试技巧,学习工作无烦恼_第3张图片

        从上面的图片中我们可以知道,编译生成的可执行文件(.exe)的大小,release版本明显较小,而debug版本明显较大。


4. VS调试快捷键

快捷键 说明
F9 插入断点和取消断点
F5 启动调试,经常用来直接跳到下一个断点处,一般是和F9配合使用
F10 逐过程,通常用来处理⼀个过程,一个过程可以是一次函数调用,或者是一条语句
F11 逐语句,就是每次都执行一条语句,这个快捷键可以让我们的执行逻辑进入函数内部。在函数调用的地方,想进入函数观察细节,必须使用F11,如果使用F10,直接完成函数调用
Ctrl+F5 开始执行不调试,此时程序直接运行起来而不调试就能直接使用

 注:调试程序需在debug版本环境下进行

1. 断点的作用是可以在程序的任意位置设置断点,打上断点就可以使得程序执行到想要的位置暂定执行,接下来我们就可以使用F10,F11这些快捷键,观察代码的执行细节。

2. 条件断点:满足这个条件,才触发断点。

3. 调试更多:了解更多VS快捷键 


5. 监视和内存观察 

5.1 监视

        在调试的过程中,我们如果要观察程序运行过程中前后变量值的变化,此时我们就要用到监视

比如观察下面的示例代码:

#include

int main()
{
	int i = 0;
	int sum = 0;
	for (i = 0; i < 14; i++)
	{
		printf("%d ", i);
		sum += i;
	}
	printf("\n总和:%d\n", sum);
	return 0;
}

步骤一:打开监视(先按下F11逐语句调试) 

学会VS调试技巧,学习工作无烦恼_第4张图片

 步骤二:监视变量、地址变化(通过对变量监视,可以更好地发现问题)

学会VS调试技巧,学习工作无烦恼_第5张图片

5.2 内存

        除了通过监视窗口观察变量值的变化,还可以通过内存窗口观察变量在内存中是如何存储的。

        还是以上面的代码为例:

步骤一:打开内存(先按下F11逐语句调试)

学会VS调试技巧,学习工作无烦恼_第6张图片

步骤二:在内存窗口中观察数据

9d3437688b534cc9b98779584126242c.png学会VS调试技巧,学习工作无烦恼_第7张图片

学会VS调试技巧,学习工作无烦恼_第8张图片

        除此之外,在调试的窗口中还有:自动窗口,局部变量,反汇编、寄存器等窗口。


6. 调试举例

6.1 举例一

求1!+2!+3!+4!的值,运行下列代码:

#include

int main()
{
	int i = 0;
	int j = 1;
	int sum = 0;
	int ret = 1;
	for (i = 1; i <= 4; i++)
	{

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

运行结果:303 

很显然,这不是我们想要得到的答案,但我们可以通过调试来找出问题

目标结果:33

运行顺序:1!=1,sum=1;2!=2,sum=3;3!=6,sum=9;4!=24,sum=33。 

调试分析过程及监视变量 

        第一次运算:未出现问题,继续调试

学会VS调试技巧,学习工作无烦恼_第9张图片

        第二次计算:未出现问题,继续调试

学会VS调试技巧,学习工作无烦恼_第10张图片

        第三次计算:出现问题

学会VS调试技巧,学习工作无烦恼_第11张图片

        第四次计算:出现问题

学会VS调试技巧,学习工作无烦恼_第12张图片

        由上可知,为什么没有得到我们预期想得到的结果?原因是在每次计算时,使用ret后未更新ret的值,以至于出现与预期结果不符的情况。 

修改后的代码:

#include

int main()
{
	int i = 0;
	int j = 0;
	int sum = 0;
	int ret = 1;
	for (i = 1; i <= 4; i++)
	{
		ret = 1; // 每次阶乘完成,更新ret的值
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);

	return 0;
}

6.2 举例二 

        在VS2022、X86、Debug 的环境下,编译器不做任何优化的话,下面代码执行的结果是啥?

#include 

int main()
{
	int i = 0;
	int arr[10] = { 0 };

	for (i = 0; i <= 12; i++)
	{
		arr[i] = 0;
		printf("haha\n");
	}

	return 0;
}

运行结果:死循环的打印“haha”。(在X86环境下运行)

        为什么会出现死循环的结果?代码运行起来不应该是数组越界访问吗?此时,我们还是要借助调试来发现问题。

        在数组未越界时,并没有出现问题

学会VS调试技巧,学习工作无烦恼_第13张图片

        而在数组越界时,我们继续调试代码可以观察到,arr[12]与i的值同步变化,此时代码运行出现死循环的情况

2ca189a7f5224aa5acdd3c517051174d.png

        为什么arr[12]与i的值会同步变化呢?我们可以猜想arr[12]与i在内存存储中的地址是相同的

37af49cec208477cbc33a80856e1ded1.png

        通过上图,我们可以验证猜想是正确的

至于为什么会出现这样的情况,我们要知道:

  1. 局部变量一般是存放在内存的栈区里的;
  2. 数组在内存中的存储:随下标的增大,地址由低到高变化;
  3. 栈区内存的使用习惯是从高地址向低地址使用的,所以变量i的地址是较大的。arr数组的地址整体是整体小于i的地址。 

图示

学会VS调试技巧,学习工作无烦恼_第14张图片

        原因:在该环境下,i和arr 数组之间恰好空出来2个整型的空间,当越界访问到arr[12]时刚好与i的地址重合,此时arr[12]与i的值同步变化,代码出现死循环。

注意:

  1. 在不同的编译器下可能中间的空出的空间大小是不一样的,代码中这些变量内存的分配和地址分配是编译器指定的,所以的不同的编译器之间就有差异了。所以这个代码是和环境密切相关的。
  2. 栈区内存的使用习惯是从高地址向低地址使用的,具体要根据编译器实现。比如:在VS上切换到X64环境,这个使用的顺序就是相反的,在Release版本的程序中,这个使用的循序也是相反的。

6.3 举例三

        在上面我们学习了如何去简单调试,那该怎么去断点调试呢?

下列代码示例:

int main() 
{
    int i = 0;
    int j = 0;
    for (i = 0; i < 88; i++) // 第一步
    {
        printf("断点调试测试");
    }

    for (j = 0; j < 44; j++) // 第二步
    {
        printf("调试");
    }
    return 0;
}

         通过观察,我们可以确认第一步没有什么问题,可能在第二步出现问题,如果慢慢调试,要调试88次才会到达第二步,这样效率就很低了。

        此时,我们可以适当地设置断点。步骤:选中要调试的行数,按F9插入断点,然后按F5开始调试。

学会VS调试技巧,学习工作无烦恼_第15张图片

        这样,可以直接完成第一步,得到运行结果:

ce855b785926499fb513d1b96b341c00.png

         还有,如果我们认为第一步的中间过程有问题,也可以在第一步插入断点,点击鼠标右键设置断点条件。

学会VS调试技巧,学习工作无烦恼_第16张图片
学会VS调试技巧,学习工作无烦恼_第17张图片

7. 编程常见错误归类

7.1 编译型错误

        编译型错误一般都是语法错误,这类错误一般看错误信息就能找到一些蛛丝马迹的,双击错误信息也能初步的跳转到代码错误的地方或者附近。编译错误,随着语言的熟练掌握,会越来越少,也容易解决。

85dde4f86a1f42cc88e3db15515c59fb.png

7.2 链接型错误

        链接型错误一般是由于:

  1. 标识符名不存在
  2. 拼写错误
  3. 头文件未包含 
  4. 引用的库不存在

b8e74ddc437049ad8e1cafbd98c15632.png

7.3 运行时错误

        运行时的错误是复杂多样的,一般需要借助调试,逐步定位问题,调试解决的是运行时问题。


8. 结语

        希望这篇文章对大家有所帮助,如果你有任何问题和建议,欢迎在评论区留言,这将对我有很大的帮助。

        完结!咻~

你可能感兴趣的:(C语言知识,c语言,学习)