• visual studio 2022调试技巧介绍


    保持空杯心态,为自己打气!

    1.什么是bug?

    bug是计算机领域专业术语,bug原意是“臭虫”,现在用来指代计算机上存在的漏洞,原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。
    漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统。

    2.调试是什么,如何调试?

    2.1 调试的概念

    调试简单来说就是调试是保证所提供的设备能够正常运行的必须程序。
    通过一些工具对代码进行检测,修复bug。

    2.2调试基本步骤

    1.确认程序是错误的
    2.用各种方法对错误进行定位
    3.用工具确认错误原因
    4.对错误提出解决方案
    5.对程序进行更改,重新测试

    3.release和debug介绍

    Debug通常是调试版本,它包含调试信息,并且不做任何优化,便于程序员调试(这个版本是专门给程序员使用的)
    release称为发布版本,它往往对代码运行速度和代码大小进行了优化,以便于用户使用。

    Debug版本和release版本比较:

    显然,release版本的空间大小要比debug空间大小要小。

    还有一点就是:release版本是没有调试的,debug版本具有调试信息,因为release版本是给用户使用的,而用户又不懂编程调试,所以就没有调试信息。

    4.visual studio 2022编辑器快捷键学习

    4.1 关键一步

    了解到debug版本是可以调试的,所以在使用visual studio 2022版本时首先环境是配置是debug版本:

    4.2 快捷键介绍

    4.2.1 常用快捷键

    4.2.1.1 生成快捷键

    Ctrl+Shift+B:生成解决方案

    Ctrl+F7:编译

    Alt+F11:对解决方案进行运行代码分析

    4.2.1.2 调试常用快捷

    Ctrl+Shift+F9:删除所有断点

    Ctrl+Alt+E:异常

    Ctrl+Alt+Q:快速监视(或者Shift+F9)

    Ctrl+Shift+F5:重启(在调试起来后使用)

    Ctrl+F10:运行到光标处

    F5:启动调试,经常和断点搭配使用。先是F9找到想要检测的位置,然后F5进行调试,可以直接从断点处检测。

    Ctrl+F5:不调试直接运行处结果。

    F11:逐语句,更加细节的观察,可以直接跳进函数内部检测。

    Shift+F11:单步跳出(跳出调试)

    F10:逐过程,可以处理一个过程(函数、语句)

    F9:创建断点和取消断点
    断点的作用就是可以让程序跳到想要的位置随意执行,继而一步步执行下去。

    Alt+6:启动内存1(需要先F10调试起来后才能使用,启动内存2:Ctrl+Alt+2;启动内存3:Ctrl+Alt+3)

    Ctrl+Alt+V+A 自动窗口(调试后才能使用)

    Ctrl+Alt+C:调用堆栈(调试后才能使用,或者用Alt+7)

    Alt+8:转到反汇编

    Ctrl+Alt+W+1:启动监视窗口1(启动监视窗口2:Ctrl+Alt+W+2;启动监视窗口3;Ctrl+Alt+W+3;启动监视窗口4:Ctrl+Alt+W+4)

    Alt+4:局部变量窗口

    Alt+5:寄存器

    Shift+F5:停止调试

    4.2.1.3 编辑常用快捷键

    Alt+向右键:完成单词(Ctrl+空格键)

    Ctrl+F:查找

    Ctrl+Shift+F:在文件中查找

    Ctrl+G:转到

    Ctrl+F12:转到声明

    F12:转到定义(前提是必须要有声明)

    Ctrl+,:导航到

    Alt+F12:查看定义

    Ctrl+H:替换

    Ctrl+Z:撤销

    Ctrl+K+C:注释所选内容

    Ctrl+K+U:取消所选注释内容

    4.2.1.4 文件常用快捷键

    Alt+F4:退出文件

    Ctrl+N:新建文件

    Ctrl+Shift+N:新建项目

    Ctrl+O:打开文件

    Ctrl+Shift+O:打开项目

    F2:重命名(鼠标必须指在文件名上才能重命名操作)

    Ctrl+Shift+S:全部保存

    4.2.1.5 项目常用快捷方式

    Shift+Alt+A:添加现有项

    Ctrl+Shift+A:添加新项

    以上快捷是根据我的默认的visual studio 2022选出的常用实用快捷键,如果在你们的编译器上不同话可根据编译器上提供的快捷消息来采取,也可以看看这个链接:visual studio 2022键盘快捷方式

    4.2.2 调试时有需求查看的信息

    4.2.2.1 查看临时变量

    4.2.2.2 查看内存消息

    4.2.2.3 查看调用堆栈

    4.2.2.4 查看汇编信息

    4.2.2.5 查看寄存器信息

    5.调试实例

    5.1 实例一

    求 1!+2!+3! …+ n! ;不考虑溢出.

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这段代码输出;

    为什么1!+2!+3!的阶乘是15呢?不应该是9吗?,下面F10调试起来看一看:

    最终解决后的代码:

    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;
    		for (j = 1; j <= i; j++)
    		{
    			ret *= j;
    		}
    		sum += ret;
    	}
    	printf("%d\n", sum);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    5.2 实例二

    问这段代码在debug版本下x86平台下,为什么是死循环?

    #include 
    int main()
    {
    	int i = 0;
    	int arr[10] = { 0 };
    	for (i = 0; i <= 12; i++)
    	{
    		arr[i] = 0;
    		printf("hehe\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面我们来通过调试来解释这个为什么?

    真有这么巧吗?
    其实并不是在VC6.0中arr[9]和i变量中间其实没有多余空间,在gcc编译器下arr[9]和i变量中间有一个整型的空间,visual studio 2022\2019\2013中的x86平台下arr[9]和i变量之间是由两个整型的空间。这些都不是巧合,而是编辑器本身规定的,不同的编译器有不同的规定。

    6.如何写好优秀代码

    6.1 优秀的代码

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

    6.2 用例题示范

    模拟实现库函数:strcpy

    首先是创建两个数组,一个数组是目标数组一个数组是源头数组,strcpy库函数就是把源头数组拷贝到目标数组上,那么我们可以用指针创建指针,分别指向两个数组起始位置,然后用指针偏移把源头数组的元素放在目标数组中,最后’\0’也放在目标数组中,从而达到实现库函数strcpy(根基思路)

    6.2.1 方法一:按照思路实现

    void my_strcpy(char* dest, char* src)
    {
    	while (*src != '\0')
    	{
    		*dest++ = *src++;
    	}
    	*dest = *src;
    }
    
    
    int main()
    {
    	char arr1[20] = "xxxxxxxxxx";
    	//xxxxxxxxxx
    	char arr2[] = "hello";
    	my_strcpy(arr1, arr2);
    	printf("%s\n", arr1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.2.2 进一步优化

    void my_strcpy(char* dest, char* src)
    {
    	//停止条件就是'\0','\0'对应的ASCII码值是0
    	while (*dest++ = *src++)
    	{
    		;
    	}
    }
    int main()
    {
    	char arr1[20] = "xxxxxxxxxx";
    	//xxxxxxxxxx
    	char arr2[] = "hello";
    	my_strcpy(arr1, arr2);
    	printf("%s\n", arr1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    6.2.3 用断言assert库函数进一步优化

    对assert函数介绍:

    简单来说,assert库函数就是为了保证指针的有效性。

    void my_strcpy(char* dest, char* src)
    {
    	assert(dest != NULL);//断言
    	assert(src != NULL);//断言
    	//NULL是#define NULL ((void *)0)
    
    	while (*dest++ = *src++)
    	{
    		;
    	}
    }
    int main()
    {
    	char arr1[20] = "xxxxxxxxxx";
    	char arr2[] = "hello";
    	my_strcpy(arr1, arr2);
    	printf("%s\n", arr1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.2.4 再一步优化

    void my_strcpy(char* dest, char* src)
    {
        assert(dest&&src);
        //NULL是#define NULL ((void *)0)
    
    	while (*dest++ = *src++)
    	{
    		;
    	}
    }
    int main()
    {
    	char arr1[20] = "xxxxxxxxxx";
    	char arr2[] = "hello";
    	my_strcpy(arr1, arr2);
    	printf("%s\n", arr1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    6.2.5 最后一步完善优化

    //strcpy有返回类型,源头数组不可变这些要求
    
    char* my_strcpy(char* dest, const char* src)
    {
    	char* ret = dest;
    	assert(dest && src);//断言
    
    	while (*dest++ = *src++)
    	{
    		;
    	}
    	return ret;
    }
    int main()
    {
    	char arr1[20] = "xxxxxxxxxx";
    	char arr2[] = "hello";
    
    	printf("%s\n", my_strcpy(arr1, arr2));
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这就是调试后慢慢对细节的慢慢优化的过程。过程比结果重要,所以重在把握过程细节。

    完善优化中const关键字再次理解

    首先我们要知道const的作用:

    告知程序员这里不能修改,具有“自描述”意思
    告知编辑器这里不能修改,如果修复就会报错

    1.const关键字修饰变量情景


    这里的const修饰的是变量,所以这个变量是不能被修改的。但是可以用指针间接修改,如:

    为什么呢?
    可以这样理解,因为const只是修饰了变量a,所以不能直接用a来修改,但是可以找到a的地址进行修改,不是直接修改a,而是间接修改,下面看const关键字修是指针的时候思路会更加清晰。

    2.const关键字修饰数组操作数请景

    这里的const修饰变量有常属性,但是这个常属性的意思是不能被修改的意思,而非真正的把变量变成常量,这是不可能的。

    3.const关键字修饰指针情景

    再来聊聊指针,int * p,这里的p是指针变量用来存放地址,*是指针的标志,int是指针的类型(决定了指针访问时访问多少字节以及指针的步长问题)

    3.1 const int *p

    3.2 int const *p

    这里int const *p和上述const int p是一样的,const都是修饰的p。

    3.3 int* const p

    3.4 const int* const p

    4.const关键字修饰函数返回值情景
    const int* test()
    {
    	static int a = 10;
    	return &a;
    }
    int main()
    {
    	int* p = test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里的const修饰函数返回值的意义就是提示程序员后期维护中,不要对其返回值进行更改。

    6.3 实现模拟strlen库函数

    int my_strlen(const char* str)
    {
        //计数器
    	int count = 0;
    	//保证指针有效性
    	assert(str != NULL);
    	//判断条件再*str='\0'时停止
    	while (*str)
    	{
    		count++;
    		str++;
    	}
    	return count;
    }
    int main()
    {
    	const char* p = "abcdef";
    	int len = my_strlen(p);
    	printf("len = %d\n", len);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    7.常见编程错误

    编译型错误:直接看错提示信息
    链接型错误:一般是标识符名不存在或者拼写错误
    运行时错误;不容易发现的bug,一般调试才能解决

    我们要做一个善于发现问题,解决问题的人,让脑袋高速飞转解决问题,这样才能成为一个厉害的人,还是要保持空杯心态,重复输入,高效输出。加油!

  • 相关阅读:
    Go-Excelize API源码阅读(三十七)——RemovePageBreak
    11.(vue3.x+vite)组件间通信方式之ref与$parent、$children
    MySQL数据库的备份
    Docker搭建Redis集群
    146.LRU缓存--hash-双链表
    allure测试报告生成逻辑--解决在Jenkins里打开allure报告页面后空白显示无数据问题(以window环境为例)
    数据库习题
    尚硅谷-设计模式篇
    JdbcTemplate环境准备_java培训
    C# 滑动验证码|拼图验证|SlideCaptcha
  • 原文地址:https://blog.csdn.net/m0_46343224/article/details/126251887