编程终极技能-调试

调试

1.调试是什么?

调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。当我程序出现了逻辑错误时就需要调试了。

2.调试的步骤

  1. 发现程序错误的存在
  2. 以隔离、消除等方式对错误进行定位
  3. 确定错误产生的原因
  4. 提出纠正错误的解决办法
  5. 对程序错误予以改正,重新测试

3. Release 和 Debug的介绍

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

例如:在VS2022中:

在这里插入图片描述

实例一
#include <stdio.h>
int main()
{
    int i = 0;
    int arr[10] = {0};
    for(i=0; i<=12; i++)
   {
        arr[i] = 0;
        printf("hehe\n");
   }
    return 0;
}

这段代码在debug 模式去编译,程序的结果是死循环。
如果是 release模式去编译,则没有死循环。

他们之间有什么区别呢?

Debug】下:

通过调试操作,发现数据访问的第12个元素刚好就是 i变量所在的空间,通过修改 arr[12]i改成了0。就这样 i 变量一旦增加到了12就会被改成0,程序发生了死循环。

原因:程序在栈上开辟空间时,优先会利用高地址的空间,因为此处i变量先创建,所以变量i的地址高于数组的地址。随后创建数组,数组的起始内存空间肯定是在i变量的下面,当数组越界访问时,访问的地址越来越高,由于iarr数组的最后一个元素的地址空间只差了8个字节,刚好数组向上多访问两个元素,就访问到了i变量,并且数组还做了将i变量置为0的操作,此时, arr[i]就又开始访问数组的第一个元素了,等到访问到arr[i](i==12)时,又将i置为0,一直循环往复,形成了死循环。

在这里插入图片描述

release】下:

release版本之下,程序不会出现死循环。因为编译器做了一些优化处理。我们可以适当观察一下:

在这里插入图片描述

可以发现:变量在内存中开辟的顺序发生了变化,影响到了程序的执行结果。

4. Visual studio的调试

4.1环境准备

首先要在编译环境中选择 Debug 选项,代码才能正常调试。

4.2快捷键的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yq2j1jW-1685019148285)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230525192232767.png)]

常用的快捷键需要记住,后期会帮我们节省很多时间。例如:shift+F11用于跳出该函数。

F5:启动调试,经常用来直接跳到下一个断点处

F9;创建断点和取消断点。断点可以在程序的任意位置设置。

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

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

CTRL + F5:开始执行但是不调试。

4.3 调试的时候查看程序信息

调试启动之后。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veoMDYaB-1685019148285)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230525192807175.png)]

查看内存信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h8C6jUQz-1685019148285)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230525192930863.png)]

总之,调试的功能应有尽有,要根据不同的实例恰当的选取。

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

常见技巧:

  • 尽量使用assert
  • 尽量使用 const
  • 养成良好的编码习惯
  • 添加必要的注释

实例二

【strcpy库函数的实现】

/***
*char *strcpy(dst, src) - copy one string over another
*
*Purpose:
*       Copies the string src into the spot specified by
*       dest; assumes enough room.
*
*Entry:
*       char * dst - string over which "src" is to be copied
*       const char * src - string to be copied over "dst"
*
*Exit:
*       The address of "dst"
*
*Exceptions:
*******************************************************************************/
char * strcpy(char * dst, const char * src)
{
        char * cp = dst;
 assert(dst && src);
 
        while( *cp++ = *src++ )
               ;     /* Copy src over dst */
        return( dst );
}

仔细观察,这里库函数的第二个参数是被const修饰的。那么这里的const有什么作用呢?

const 的作用

#include <stdio.h>
//代码1
void test1()
{
    int n = 10;
    int m = 20;
    int *p = &n;
    *p = 20;//编译通过
    p = &m; //编译通过
}
void test2()
{
     //代码2
    int n = 10;
    int m = 20;
    const int* p = &n;
    *p = 20;//编译失败
    p = &m; //编译通过
}
void test3()
{
    int n = 10;
    int m = 20;
    int *const p = &n;
    *p = 20; //编译通过
    p = &m;  //编译失败
}
int main()
{
    //测试无cosnt的
   test1();
    //测试const放在*的左边
    test2();
    //测试const放在*的右边
    test3();
    return 0;
}

const修饰指针时:

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

assert 的作用

assert的作用是:当程序运行时,若assert的括号中的条件不满足时,程序会强制提醒程序员。例如:

但是assert需要包含头文件:#include<assert.h>

#include<stdio.h>
#include<assert.h>
int div1(int a, int b)
{
	assert(0);
	int ret = a / b;
	return ret;
}
int main()
{
	int a = 4;
	int b = 1;
	//scanf("%d %d", &a, &b);
	int ret = div1(a, b);
	printf("%d", ret);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NzPX5UmM-1685019148286)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230525205057929.png)]

只要程序不满足assert括号中的语句,程序就会强制提醒用户出错的地方。

小练习:模拟实现strlen函数

#include<stdio.h>
#include<assert.h>
//模拟实现strlen函数
int MyStrlen(const char* arr)
{
	assert(arr != NULL);
	int count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;
}
int main()
{
	char arr[1000];
	gets(arr);//读取字符串
	int len = MyStrlen(arr);
	printf("%d \n", len);
	return 0;
}

这里要注意assert的使用和 const的使用。

6. 编程常见错误

编译型错误:

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单.

链接型错误:

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。

运行时错误:

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

所以,还是要靠我们做一个有心人,积累排错经验。

7.完结

本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里是彪彪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值