实用调试技巧与案例分析

news2024/10/7 6:42:50

目录

调试(Debug):

调试的基本步骤:

Debug和Release的介绍:

几个常用的快捷键:

案例一:

案例二:

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

案例一:

1.assert用法

2.const用法

案例二:

编程常见的错误


调试(Debug):

又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。

调试的基本步骤:

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

Debug和Release的介绍:

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

二者区别:

  1. 从生成文件的大小来看:Debug的文件比Release的文件大;
  2. Debug可以用于代码的调试,而Release不能;
  3. Debug不对代码进行优化,而Release则对代码进行优化

几个常用的快捷键:

  1. F5:启动调试,经常用来直接跳到下一个断点处,常和F9搭配使用;
  2. F9:创建断点和取消断点。断点的作用:可以在程序的任意位置设置断点。
  3. F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句;
  4. F11:逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们进入函数内部,这也是最长用的一个快捷键;
  5. CTRL + F5:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。

案例一:

实现代码:求1!+2!+3!+...+n!的值

先思考如何用代码实现求n!

int main()
{
	int n = 0;
	printf("请输入一个正整数n:");
	scanf("%d",&n);

	int i = 0;
	int ret = 1;

	for (i = 1; i<=n; i++)
	{
		ret = ret * i;
	}

	printf("%d\n",ret);

	return 0;
}

 通过调试可以发现,ret=1*2*3=6,代码运行正确,程序逻辑没有问题

接着我们试着去求1!+2!+3!+...+n!

int main()
{
	int n = 0;
	scanf("%d",&n);

	int i = 0;
	int j = 0;
	int sum = 0;
	int ret = 1;

	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}

		sum += ret;
	}

	printf("%d\n",sum);

	return 0;
}

当n=4时,通过运行程序可以发现结果却不等于33,那问题出在哪?通过进一步调试可以发现,当我们在求3!的阶乘时,ret的值本该等于6,但是最后的结果却是12,可想而知,问题应该是出现在求一个数的阶乘上。当我们在求2!时,ret的值等于2,但是当n++变为3的时候,我们发现ret的依旧等于2,此时的j=1<i=3,所以ret会继续*1*2*3,也就是将值变为了2*1*2*3=12,所以这才是ret=12的由来。可见,在求一个数的阶乘时,ret的值并没有初始化为1,而是将上一次运行时的结果累乘到本次运算上,进而导致问题的出现。我们只要在对某个数求阶乘之前,将ret初始化为1即可避免问题的发生。

int main()
{
	int n = 0;
	scanf("%d",&n);

	int i = 0;
	int j = 0;
	int sum = 0;
	int ret = 1;

	for (i = 1; i <= n; i++)
	{
		ret = 1;
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}

		sum += ret;
	}

	printf("%d\n",sum);

	return 0;
}

改进版:

int main()
{
	int n = 0;
	scanf("%d",&n);

	int i = 0;
	int sum = 0;
	int ret = 1;

	for (i = 1; i <= n; i++)
	{
		ret *= i;
		sum += ret;
	}

	printf("%d\n",sum);

	return 0;
}

案例二:

研究程序死循环的原因

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和arr[12]的地址竟是相同的,说明它们是共用一块内存空间。因此我们猜测,在对i的值进行修改时也会导致arr[12]的变化。

数组下标即便是越界的,在运行到arr[10]和arr[11]时, 同样会将其初始化为0。另外,我们发现,arr[12]的值确实是随着i进行变化的,说明我们的猜测是正确的。

当i=12时,将arr[12]的值也为12。但是,当运行到arr[12]=0时,i的值也在此刻同时变化为0。此时i的值为0依旧满足条件,所以又将进行下一轮循环。

这里面是有原因的,当然也有一定程度的巧合

  1. i 和arr 是局部变量,而栈区一般存放的是局部变量以及函数参数
  2. 栈区的使用习惯:先使用高地址处的空间,再使用低地址处的空间
  3. 数组随着下标的增长,地址是由低到高变化的 

所以arr和i在栈区的空间布局 ,应该是如下图所示:

那如何避免死循环的发生?

  1. 先定义arr数组再定义 i;
  2. 控制循环次数,i<=9

需要注意的是,不同编译器下局部变量 i 和 arr 在内存中的布局是不同的:

  1. 在vc6.0中i和arr数组之间没有空隙,即i<=10就陷入死循环
  2. 在gcc中i和arr数组之间空出一个int空间,即i<=11就陷入死循环

最后, Release会对代码进行优化使之不会进入死循环。所以,上面的程序在Release下运行是不会陷入死循环的,它会直接打印13遍“hehe”。

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

优秀的代码:

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

常见的coding技巧:

  1. 使用assert;
  2. 尽量使用const;
  3. 养成良好的编码风格;
  4. 添加必要的注释;
  5. 避免编码的陷阱

案例一:

模拟实现库函数strcpy

头文件:string.h

原型声明:char *strcpy(char* dest, const char *src)

功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间(连同字符串串结束标志’\0’也一并拷贝)

初阶版:

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

改进版:

char* my_strcpy(char* dest,const char* src)
{
	assert(dest&&src);//断言:如果表达式dest != NULL为假就会报错
	
	char* ret = dest;//保存目标空间的地址
	//注意:字符'\0'就是数组0
	while (*dest++ = *src++)
	{
		;
	}

	return ret;//返回目标空间的地址
}

通过比对初阶版和改进版的区别,对知识点进行下列总结:

1.assert用法

assert是个宏,并非是个函数。assert 宏的原型定义在 assert.h 中,其作用是如果它的条件返回错误,则终止程序执行。

#include "assert.h" 
void assert( int expression );

assert 的作用是计算表达式 expression ,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。

使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。

assert是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为false(0),程序将报告错误并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏常用来判断程序中是否出现了明显非法的数据,如果出现了则终止程序以免导致严重后果,同时也便于查找错误。

assert只有在 Debug 版本中才有效,如果编译为 Release 版本则被忽略。

2.const用法

const放在*的左边(const int* p)
const修饰的是*p,表示p指向的对象不能通过p来修改,但是p变量中的地址是可以改变的
const int*p=int const*p

int main()
{
	const int num = 10;

	const int* p = &num;//加上const之后,值就不能进行修改
	//int const* p = &num;

	int n = 100;
	p = &n;//可以修改
	//*p = 20;//不能进行修改
}

const放在*的右边(int* const p)
const修饰的是p,表示p的内容不能被改变,但是p指向的对象是可以通过p来改变的

int main()
{
	const int num = 10;

	int* const p = &num;//const限制的是p,p不可以进行修改

	*p = 200;//可以修改

	int n = 100;
	//p = &num;//不能修改
}

案例二:

模拟实现库函数strlen

#include<assert.h>
int my_strlen(const char* str)
{
	int count = 0;
	assert(str);
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	int len = my_strlen("abcdef");

	printf("%d\n",len);
	return 0;
}

编程常见的错误:

常见的错误分类:

  1. 编译型错误:直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
  2. 链接型错误:看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。
  3. 运行时错误:借助调试,逐步定位问题。最难搞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/598228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

离散数学-数理逻辑

《离散数学》是计算机专业的一门十分重要的专业基础课。离散数学作为有力的数学工具对计算机的发展、计算机研究起着重大的作用。目前&#xff0c;计算机科学中普通采用离散数学中的一些基本概念、基本思想和基本方法。通过本课程的学习&#xff0c;掌握数理逻辑、集合论、代数…

6月1号软件资讯更新合集......

Chrome 114 正式发布&#xff0c;支持 CHIPS 自 Chrome 113 发布以来&#xff0c;已经过了四个星期&#xff0c;Google 近日也准时发布了 Chrome 114。Chrome 114 默认启用了 CHIPS&#xff0c;这是 Google 通过新的 cookie 属性来淘汰第三方 Cookie 的一部分&#xff1b;Chro…

利用Git及GitHub对项目进行版本控制

目录 一、在本地安装Git 二、利用Git将项目上传到Github上 三、用HTTPS获取GitHub上的项目 四、版本控制 一、在本地安装Git 1、Git安装链接&#xff1a;https://git-scm.com/downloads 2、下载安装包&#xff0c;双击exe文件进行安装&#xff1a; 3、接下来会弹出一系列…

Nginx服务基础、访问控制、虚拟主机

Nginx服务基础、访问控制、虚拟主机 一、Nginx介绍二、Linux系统Nginx安装1、官网下载Nginx压缩包2、编译安装Nginx1.配置环境2.安装依赖包3.创建运行用户、组4.编译安装5.检查、启动、重启、停止 nginx 服务6.添加Nginx系统服务 三、Nginx配置文件1、全局配置2、I/O 事件配置3…

水务漏损管理中存在的问题及解决方法

原文链接https://mp.weixin.qq.com/s?__bizMzg3NzkxNTI1MA&mid2247484559&idx1&snd1402e3f9fc75f7483a9dca3fc0174d4&chksmcf1af992f86d7084c48ce7e4072fd6be0555ec086c1065ef83398390c8bd19f2560daf594d7c&token955052059&langzh_CN#rd 在供水建设管…

Codeforces Round 875 div.2 problemB. Array merging题解

目录 一、题目 二、题目分析 三、 一、题目 传送门 B. Array merging time limit per test 1 second memory limit per test 256 megabytes input standard input output standard output You are given two arrays a and b both of length n. You will merge††…

Python获取各大企业招聘需求以及可视化分析展示

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 课程亮点 1、爬虫的基本流程 2、可视化分析展示 3、requests模块的使用 4、保存csv 开发环境: python 3.8 运行代码 pycharm 2022.3.2 辅助敲代码 专业版 模块使用: 内置模块: import pprint >>> 格式化输入…

Linux提权:定时任务 环境变量 配置不当 数据库

目录 环境变量配合SUID 实战测试 原理分析 实战中如何发现挖掘这类安全问题 定时任务打包配合SUID 原理分析 定时任务权限配置不当 数据库提权-梭哈的艺术 隧道出网提权 环境变量配合SUID 实战测试 这个提权方法比较鸡肋&#xff0c;因为它需要两个前提条件&#xf…

C语言:函数栈帧

寄存器&#xff1a; eax ebx ecx edx ebp esp 必须理解这两个寄存器&#xff08;寄存器是独立于内存的&#xff09; 这两个寄存器存放的是地址&#xff0c;用来维护函数栈帧&#xff08;正在调用哪个函数&#xff0c;两个寄存器就维护哪个函数的栈帧&#xff09; 每一个函数都…

C# NX二次开发:实现制图模块复制Sheet页,并且获取其中表格

今天要讲的是如何在NX中实现制图模块当前Sheet页的复制&#xff0c;并且获取Sheet页中的表格。首先简单介绍一下NX的制图模块是做什么的。 在NX中建模模块是用来绘制模型的&#xff0c;而想要将模型的相关尺寸投出来直观的看到&#xff0c;就要用到制图模块。 在制图模块中可…

leetcode--从二叉搜索树到更大和树(java)

从二叉搜索树到更大和树 leetcode -1038 题 从二叉搜索树到更大和树解题思路代码演示二叉树专题 leetcode -1038 题 从二叉搜索树到更大和树 原题链接&#xff1a; https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/ 题目描述 给定一个二叉搜索树 root (B…

Chrome提示由贵单位管理该怎么取消?

如果你的 Chrome处于托管&#xff0c;你的管理员是可以设置或限制一些特定功能、可以安装一些应用、监视活动以及控制您的使用方式。 如何知道是否托管&#xff1a; 打开 Chrome 。在右上角&#xff0c;选择“更多”图标 。查看菜单底部。如果您看到“由贵单位管理”&#xff…

泡利矩阵(一)

〇、厄米矩阵 厄米矩阵&#xff08;Hermitian Matrix&#xff09;&#xff0c;也称为自共轭矩阵&#xff08;Self-adjoint Matrix&#xff09;&#xff0c;是线性代数中的一个重要概念。它是指一个复数域上的方阵&#xff0c;其转置矩阵与共轭矩阵相等。 具体来说&#xff0c…

15稳压二级管

目录 一、基本原理 二、I-V特性 三、工作原理 四、参数 1、Vz 2、Zzt和Zzk 3、IrVr 4、VfIf 5、Pd 五、应用 1、示例1 2、串联应用 3、钳位电路 六、动态电阻 一、基本原理 稳压二极管或“击穿二极管”(有时也称为齐纳二极管)基本上与标准PN结二极管相同&#xf…

Linux NGINX服务

NGINX与Apache对比 轻量级&#xff0c;Nginx比apache 占用更少的内存及资源&#xff1b;静态处理&#xff0c;Nginx 静态处理性能比 Apache 高 &#xff1b;Nginx可以实现无缓存的反向代理加速&#xff0c;提高网站运行速度&#xff1b;Nginx的性能和可伸缩性不依赖于硬件&…

multipass基础入门,搭建本地迷你云,一个比VMware轻量的虚拟机软件

介绍 multipass是一款轻量&#xff0c;且开源的虚拟机。 Multipass是一个灵活、强大的工具&#xff0c;可用于多种用途。在其最简单的形式下&#xff0c;它可以用来在任何主机上快速创建和销毁Ubuntu虚拟机&#xff08;实例&#xff09;。在更全面的情况下&#xff0c;Multip…

redhat9 shell脚本判断磁盘、判断web运行、curl测试web(及一些报错纠正)

1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。 2、判断web服务是否运行&#xff08;1、查看进程的方式判断该程序是否运行 2、通过查看端口的方式判断该程序是否运行&#xff09;&…

关于这款开源的ES的ORM框架-Easy-Es适合初学者入手不?

前言 最近笔者为了捡回以前自学的ES知识&#xff0c;准备重新对ES的一些基础使用做个大致学习总结。然后在摸鱼逛开源社区时无意中发现了一款不错的ElasticSearch插件-Easy-ES&#xff0c;可称之为“ES界的MyBatis-Plus”。联想到之前每次用RestHighLevelClient写一些DSL操作时…

微信小程序 nodejs+vue+python家校通家校联系作业系统

家本系统有家长&#xff0c;教师&#xff0c;管理员三个角色&#xff0c;家长可以注册登陆小系统&#xff0c;查看公告&#xff0c;查看教师布置的作业&#xff0c;上传孩子的作业&#xff0c;查看学生成绩&#xff0c;成绩统计&#xff0c;家长在线发贴交流&#xff0c;在线留…

009 - STM32学习笔记 - 中断

009 - STM32学习笔记 - 中断 这节的内容&#xff0c;野火的官方视频我反复看了好几次&#xff0c;但是感觉火哥在这块讲解的特别绕&#xff0c;理解起来很吃力&#xff0c;后来在看了一下其他老师的视频&#xff0c;结合一些书本资料和官方手册&#xff0c;才搞清楚STM32中断该…