【调试技巧】一名优秀的程序员是一名出色的侦探

本文介绍了调试的基本概念,Debug和Release模式的差异,并详细讲解了在Windows环境下,特别是使用VS2019进行C语言调试的方法,包括设置断点、查看变量值、内存信息、调用堆栈和反汇编代码。此外,文章还探讨了如何写出易于调试的代码,并通过实例分析了assert和const的使用,强调了它们在编程中的重要性。
摘要由CSDN通过智能技术生成

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在回炉重造C语言(2023暑假)
✈️专栏:【C语言航路】
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


一、调试是什么?

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

二、Debug和Release的介绍

在这里插入图片描述

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

例如如下代码,在DebugRelease环境下分别做了哪些优化呢?

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <= 12; i++)
	{
		printf("hello world!\n");
		arr[i] = 0;
	}
	return 0;
}

【Debug环境】

在这里插入图片描述

如上图所示,在Debug环境下,程序的结果是死循环。而为什么会死循环呢?在后面调试的时候会讲解到。

【Release环境】

在这里插入图片描述

如上图,在Release环境下,程序并没有发生死循环。这就是因为优化导致的。

三、Windows环境调试介绍

注:本篇文章以VS2019开发工具为例

3.1 调试环境准备

在这里插入图片描述

环境一定要选择Debug选项,才能使代码正常调试,Release环境下是不能调试的。

3.2 快捷键(重点)

最常用的几个快捷键:

  1. F5:启动调试,经常用来直接跳到下一个断点处
  2. F9:选中一行创建断点和取消断点。断点的作用:当代码量大的时候,可以在程序的任意位置设置断点,这样就可以使得程序在想要的位置随意停止执行。F9一般是配合F5来使用的。
  3. F10:通常用来处理一个过程。意思就是当遇到函数时按此快捷键是看不到自定义函数内部的细节,或者还可以处理一条语句。
  4. F11:每次都只执行一条语句。此快捷键看似和F10好像差不多,但它最重要的是可以使我们的执行逻辑进入到函数内部(这才是最常用的)
  5. Ctrl + F5:开始执行不调试,就是可以直接运行程序查看结果

当然还有很多的快捷键:点我跳转

3.3 调试时查看程序的当前信息(介绍常用的)

注意:在使用以下功能之前要先按下F10

3.3.1 查看临时变量的值

首先先按下F10,在根据以下步骤操作

在这里插入图片描述

然后就可以一步一步按F10逐语句来观察值的变换:

在这里插入图片描述

这里在提一嘴,当数组传参时,由于传参传的是首元素地址,因此我们在监视只能观察到数组的第一个元素:

在这里插入图片描述

那如何能观察到数组的所有元素呢?数组名,数组元素个数

在这里插入图片描述

3.3.1 查看内存信息

首先先按下F10,在根据以下步骤操作:

在这里插入图片描述

可以观察变量在内存中是如何存储的:

在这里插入图片描述

3.3.3 查看调用堆栈

首先先按下F10,在根据以下步骤操作:

在这里插入图片描述

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

在这里插入图片描述

通过上图我们发现:其实在调用main函数之前还调用了其他的函数。

再举个例子:

在这里插入图片描述

通过上图可以发现:arr_test函数是被main函数调用的。

3.3.4 查看反汇编

有两种方式可以查看反汇编:

  1. 先按F10,然后右击鼠标,选择反汇编

在这里插入图片描述

  1. 先按F10,剩下步骤如下图所示:

在这里插入图片描述

如何看反汇编,建议大家可以看看我的往期博客:函数栈帧的创建和销毁

四、调试的实例

刚刚讲过以下代码在Debug环境下会死循环,现在我们通过调试来分析为什么会造成死循环

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <= 12; i++)
	{
		printf("hello world!\n");
		arr[i] = 0;
	}
	return 0;
}

【调试结果】

在这里插入图片描述

当在调试的过程中,我发现i的值的变化也会影响arr[12]值的变化,于是我观察它们的地址,发现它们竟然用的是用一块空间,导致当arr[12]被修改成0,i随之也被修改成0继续循环,最终导致了死循环

接下来我用图来为大家解释为什么iarr[12]可能会使用同一块空间地址(注:本环境是在vs2019 x86,不同的环境可能会造成不同的结果)

在这里插入图片描述

五、如何写出易于调试的代码

5.1 优秀的代码

  1. 代码能够正常运行(最基本的)
  2. Bug尽可能少
  3. 效率高
  4. 可读性高(要让别人看得懂)
  5. 可维护性高
  6. 注释清晰(难理解的代码加上注释后更容易理解)
  7. 文档齐全

5.2 如何写出好的代码

  1. 使用assert(后面会介绍)
  2. 尽量使用const(后面会介绍)
  3. 养成良好的编码风格(比如取变量名要有意义)
  4. 调价必要的注释
  5. 避免编码陷阱(如:数组越界)

六、 实例:模拟实现strcpy(讲解assert、const的使用)

【strcpy文档】

文档地址:点击跳转

在这里插入图片描述

【代码实现】

#include <stdio.h>
#include <assert.h>
// 函数原型:
//char * strcpy (char * destination, const char * source)

// 返回值:目标空间的起始地址要被返回char*
char* my_strcpy(char* dest, const char* sour)
{
	// 记录目标空间的起始地址
	char* res = dest;

	// 判断指针的有效性
	assert(dest != NULL && sour != NULL);

	// 赋值拷贝
	while (*sour != '\0')
	{
		*dest = *sour;
		dest++;
		sour++;
	}
	// 来到此处'\0'还未被拷贝
	*dest = *sour;
	
	// 返回值
	return res;
}
int main()
{
	// 将arr1的内容拷贝到arr2中
	char arr1[] = "hello world!";
	char arr2[20] = { 0 };
	//       目的地  源头
	my_strcpy(arr2, arr1);

	printf("%s\n", arr2);
	return 0;
}
  1. 什么是assert是及好处

assert是断言,是一个暴力的检查方法。括号内可以放表达式,如果表达式的结果为假,就会报错;如果表达式的结果为真,则什么事都不发生。它的好处:可以准确的告诉我们哪里发生了错误。 注意:assert需要包含头文件#include <assert.h>
在这里插入图片描述

  1. 为什么形参需要用const修饰

const修饰的变量不能被修改。假设有一个“糊涂”的程序员不小心将*dest = *sour写成*sour = *dest,导致不需要修改的空间被修改了。加上了const更安全。

六、补充:const的作用

首先先来看看一下代码:

#include <stdio.h>
int main()
{
	int n = 985;
	int m = 211;
	printf("n的地址:%p\n", &n);
	printf("n的地址:%p\n", &m);
	int* p = &n;
	printf("修改前p的地址:%p\n", p);
	*p = 20;
	printf("n = %d\n", n);
	p = &n;
	printf("修改后p的地址:%p\n", p);

	return 0;
}

【程序结果】

在这里插入图片描述

通过以上代码我们发现:可以通过指针来间接修改变量的值,同时指针变量也能被修改

假设const*的前面,结果会是如何呢?

int main()
{
	int n = 985;
	int m = 211;
	const int* p = &n;
	*p = 20;
	p = &n;

	return 0;
}

【程序结果】

在这里插入图片描述

我们发现,*p不能被修改。这是因为const放在*的左边,修饰的是指针指向的内容,也就是说,指针指向的内容不能被修改。

那假设const*的后面结果又会是如何呢?

int main()
{
	int n = 985;
	int m = 211;
	int* const p = &n;
	*p = 20;
	p = &n;

	return 0;
}

【程序结果】

在这里插入图片描述

我们发现变量p不能被修改。这是因为此时的const修饰的是指针变量本身,要保证指针变量的内容不能被修改。

总结

  1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可修改。
  2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值