实用调试技巧【下篇】

  • Post author:
  • Post category:其他


在这里插入图片描述

🔴


本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解


在这里插入图片描述



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



3.2.1.查看临时变量的值

🔴

自动窗口


👉调试–>窗口–>自动窗口

在这里插入图片描述

在这里插入图片描述

它会出现一个自动窗口,它会自动捕获变量的值,让你监视,方便的我们不用手动添加变量,但是如果它不想让你看的时候过了一会就会自动取消了,我们就监视不到了

🔴

局部变量


👉在自动窗口的下面

在这里插入图片描述

与自动窗口比较相似,但不同的是放的是局部变量,监视的是程序执行中的

局部变量


🔴

监视


👉调试–>窗口–>监视

在这里插入图片描述

这才是我们用到最多的调试功能

在这里插入图片描述

我们想监视谁,就输入谁,它不会自己取消,会一直显示,来为我们提供对变量的监视

在这里插入图片描述

在这里插入图片描述

数组也可以观察,变量的地址也都可以观察到

如果有需要,可以同时打开4个监视窗口,每个窗口都可以拖动,放到你想放的位置



3.2.2.查看内存信息

🔴

内存


👉调试–>窗口–>内存

在这里插入图片描述

在这里插入图片描述

☝️


内存中本放的是二进制的数据,但是为了展示方便,所以是以十六进制来显示的


☝️



3.2.3.查看调用堆栈

🔴

调用堆栈


👉调试–>窗口–>调用堆栈

在这里插入图片描述

👇简单知道一下 什么是栈👇

在这里插入图片描述

👇看这段代码👇

在这里插入图片描述

👇

调用堆栈

👇

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

☝️


反映的是函数的调用逻辑


☝️



3.2.4.查看汇编信息

🔴

反汇编


👉在内存的下面

在这里插入图片描述

👇看到的是c语言代码翻译出来的汇编代码👇

在这里插入图片描述



🥳4.调试实例

👇看下面这段代码👇

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

运行起来是什么样呢?

在这里插入图片描述

数组已经越界了,为什么没有崩溃而是死循环了呢?

👇进入调试👇

在这里插入图片描述


i

等于9的时候一切都正常,我们继续👇

在这里插入图片描述

这个代码真的是在越界访问!胆大包天啊,如果我们继续下一步呢👇

在这里插入图片描述

继续一直执行

在这里插入图片描述

再改

在这里插入图片描述


i

到12之后并没有++变成13, i 再一直跟着

arr[12]

改变,陷入了死循环,这是为什么呢?有没有可能它们是在同一个空间呢?我们看一下👇

在这里插入图片描述

地址居然一摸一样,它俩在一个空间😕

那么底层原理什么呢?为什么会这样呢?👇

🔴

原理:

1

. i 和 arr 是局部变量,局部变量是放在栈区上的

2. 栈区内存的使用习惯是 使用高地址的空间,再使用低地址处的空间

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

在这里插入图片描述



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

🥰

优秀的代码:


🙌 1. 代码运行正常

🙌 2. bug很少

🙌 3. 效率高

🙌 4. 可读性高

🙌 5. 可维护性高

🙌 6. 注释清晰

🙌 7. 文档齐全

🥰

常见的coding技巧:


🙌 1. 使用assert

🙌 2.尽量使用const

🙌 3. 养成良好的编码风格

🙌 4. 添加必要的注释

🙌 5. 避免编码的陷阱



5.1.模拟实现库函数: strcpy

在这里插入图片描述

拷贝一个字符串

示例👇

#include<stdio.h>
#include<string.h>

int main()
{
	char arr1[] = "hello world";
	char arr2[20] = { 0 };
	strcpy(arr2, arr1);
	printf("%s\n", arr2);

	return 0;
}

在这里插入图片描述

👇

模拟实现 strcpy

👇

#include<stdio.h>
#include<string.h>
void my_strcpy(char* dest, char* src)
{
	while (*src != 0)
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = *src; // \0的拷贝
}
int main()
{
	char arr1[] = "hello world";
	char arr2[20] = { 0 };
	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);

	return 0;
}

如果写成这样的代码,这算一个好的代码吗?当然不算一个好的代码,我们可以优化一下👇

void my_strcpy(char* dest, char* src)
{
	while (*src != 0)
	{
		*dest++ = *src++;
	}
	*dest = *src; // \0的拷贝
}

我们还可以再融合一下👇

void my_strcpy(char* dest, char* src)
{
	while (*dest++ = *src++)
	{
		;
	}
}

可以直接一次搞定

这样改进完好像已经足够好了,但是我们还能再改进👇

void my_strcpy(char* dest, char* src)
{
	if (dest == NULL || src == NULL)
	{
		return;
	}
	while (*dest++ = *src++)
	{
		;
	}
}

防止遇到空指针,但是这样处理遇到问题只是回避掉了,并不做处理👇

#include<stdio.h>
#include<string.h>
#include<assert.h>
void my_strcpy(char* dest, char* src)
{
	//断言
	assert(dest != NULL);
	assert(src != NULL);

	while (*dest++ = *src++)
	{
		;
	}
}
int main()
{
	char arr1[] = "hello world";
	char arr2[20] = { 0 };
	my_strcpy(arr2, NULL);
	printf("%s\n", arr2);

	return 0;
} 

在这里插入图片描述

使用

断言

处理之后,如果有问题它就会报错,把问题抛出来并且告诉你出错在哪里,如果不使用断言执行程序的话代码就崩掉了,不会告诉你哪里有问题👇

在这里插入图片描述

🚨

断言是对程序员非常友好的东西,我们使用断言是个很好的编程习惯


🚨使用

断言

别忘记引用头文件

<assert.h>

void my_strcpy(char* dest, char* src)
{
	//断言
	assert(dest && src);

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

简便一点可以直接写成这样☝️



5.2. const 修饰指针

在这里插入图片描述


const

修饰变量

m

之后,

m

的值就更改不了了

但是可以使用一些小聪明把

m

改掉👇

int main()
{
	const int m = 10;
	//m = 20;//error
	int* p = &m;
	*p = 20;
	printf("%d\n", *p);

	return 0;
}

在这里插入图片描述

这个代码非常奇葩,因为

const

只是在语法层面限制了

m

不能改,但是通过

地址

是可以更改的

我们可以在语法层面把指针

p

也进行限制👇

在这里插入图片描述

int main()
 {
	int n = 100;
	const int m = 10;
	//m = 20;//error
	//const 修饰指针
	//1. const放在*的左边,*p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量
	const int* p = &m;
	p = &n;

	printf("%d\n", *p);

	return 0;
 } 

在这里插入图片描述

int main()
 {
	int n = 100;
	const int m = 10;
	//m = 20;//error
	//const 修饰指针
	//2. const放在*的右边,限制的是p,p不能改变,但是p指向的内容*p,是可以通过p来改变的
	int* const p = &m;
	*p = n;

	printf("%d\n", *p);

	return 0;
 } 

在这里插入图片描述


🤜

1. const放在

的左边,

p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量




🤜

2. const放在的右边,限制的是p,p不能改变,但是p指向的内容p,是可以通过p来改变的

void my_strcpy(char* dest,const char* src)
{
	//断言
	assert(dest && src);

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

所以上面模拟实现

strcpy

的代码这样写就更严谨了,加上了

const

来修饰

char* src


🥰提高了代码的

健壮性(鲁棒性)


因为如果下面的 *dest++ = *src++ 位置要是不小心写反了程序就会报错,所以这样写才是有意义的!

我们再来进行最后一次优化👇

#include<stdio.h>
#include<string.h>
#include<assert.h>

//strcpy函数返回的是目标空间的起始位置
char* my_strcpy(char* dest,const char* src)
{
	//断言-- 保证指针的有效性
	assert(dest && src);
	char* ret = dest;
	//把src指向的字符串拷贝到dest指向的数组空间,包括\0字符
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[] = "hello world";
	char arr2[20] = { 0 };
	//链式访问
	printf("%s\n", my_strcpy(arr2, arr1));

	return 0;
} 

🥰这些都是一些技巧,希望大家可以理解🥰



🥳6.编程常见的错误

🥰

优秀的代码:

🔴

1. 编译型错误


👉直接看错误提示信息 解决问题,或者凭借经验就可以搞定,相对简单

🔴

2. 链接型错误


👉看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是

标识符名不存在

或者

拼写错误

(ctrl + F 可以进行搜索)

🔴

3. 运行时错误


👉借助调试,逐步定位问题,最难搞

🚨


做个用心的人!积累排错经验!


总结🥰


本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解


以上就是调试技巧下篇内容啦🥳🥳🥳🥳

希望我们可以做一个用心的人💕💕💕

小的会继续学习,继续努力带来更好的作品😊😊😊

创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰


请添加图片描述



版权声明:本文为weixin_74937672原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。