C语言——VS2019实用调试技巧

news2024/9/21 18:32:43

前言

要想成为一个合格的程序员,不仅仅要会写代码,更要会调试代码。咔咔一通敲代码,敲出了BUG,这时就分两种程序员,一种是质疑编译器的程序员,“什么?我写出了BUG,是不是机器出了问题?”,然后接着就是迷信式地调试,直至解决BUG。另一种是默默地高效地调试代码,把BUG给修复了。一个合格的程序员不但要会写BUG,还要懂得通过调试来修复BUG,毕竟第一个人想写BUG那是拦都拦不住的。本篇文章案例所使用环境为VS2019

1.什么是BUG?

bug在英文中的意思是虫子,这又为什么会和计算机程序沾边呢?原来在1947年9月9日,技术员赫柏对继电器编程完毕后,尝试运行,发现继电器无法正常工作。在排查问题后,发现是一只飞蛾飞到了继电器里,触电身亡后,它的尸体导致了机器卡住。
因此在提交错误报告的时候,赫柏直接将飞蛾贴在了报告了,并标了“bug”,从而导致bug有了后面的衍生意。这也是计算机程序的第一个bug。
在这里插入图片描述

BUG通常指的是计算机程序程序因为编写错误从而产生的错误以及漏洞。

2.调试的重要性

2.1.什么是调试?

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

2.2.调试的基本步骤

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

程序编写时的常见错误

通常程序的错误分为以下三种:

1、编译型错误
2、链接型错误
3、运行时错误

1、编译型错误:通常是指语法语法出现了问题而产生错误。
在这里插入图片描述

2、链接型错误:通常是指标识符名不存在或者拼写错误。
在这里插入图片描述

此时编译这段代码,编译器并没有报错。control + F5让代码运行起来。
在这里插入图片描述
当代码较多时,不方便一眼看出拼写错误,可以通过control+F 文本查找功能,输入错误的标识符来定位。
在这里插入图片描述

3、运行时错误:当编译和运行代码都没有问题时,程序此时运行发生了错误。这也是最难处里的错误。此时就需要借助调试,逐步定位问题。例如:

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

在这里插入图片描述
在这里插入图片描述
这个BUG我会在后面进行调试修复的演示。

2.3.debug版本与release版本的介绍

debug版本:通常被称为调试版本。它包含了调试信息,不对程序进行优化,便于程序员调试程序。
release版本:通常被称为发布版本。它不包含调试信息,所以无法进行调试。release版本对程序进行了各种的优化,使得程序的代码大小以及运行速度都是最优的,便于用户的使用。

//本段代码在debug版本与release版本的的代码大小区别

#include <stdio.h>
int main()
{
char str[]=“hello world!”;
printf(“%s\n”,str);
return 0;
}
debug版本与release版本的exe.文件的大小对比
在这里插入图片描述
在这里插入图片描述

//本段代码在debug版版本与release版本的区别
#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版本下程序死循环打印hehe。
在这里插入图片描述
release版本下程序并没有死循环打印hehe
在这里插入图片描述
这也是说明了程序在release版本下会进行优化。至于为什么在debug版本下会死循环打印hehe呢?首先可以肯定的错误是数组越界访问了,至于为什么会导致程序死循环呢?待会我会对此进行分析。

以上就是release版本与debug版本的一些区别。程序在release版本为什么不能调试?这里我就不做演示,下面将介绍VS2019环境下如何调试。如果你对此感兴趣,不妨在看完下文的介绍后上手试试看看,毕竟实践出真知。

3.VS环境调试介绍

补充:本文使用环境为VS2019。VS环境下,调试的方式和调试的逻辑大同小异。

3.1.环境准备

在当前环境下使用debug模式,这样才能正常的进行调试。
在这里插入图片描述

3.2如何开始调试?

开始调试步骤如下:
在这里插入图片描述
开始调试后,打开窗口菜单后,如图所示
在这里插入图片描述

未开始调试,打开窗口菜单。如图所示
在这里插入图片描述
所以进入调试是需要按快捷键或者在调试菜单中单击开始调试(s)进入调试。

3.3学会使用快捷键进行调试

合理的使用快捷键可以大大地提高我们的调试效率,所以学会使用快捷键进行调试是很重要的。
常用的快捷键:
F5

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

F9

创建或取消断点。

补充断点是在特定点暂停程序执行的特殊标记,使用断点可以检查当前程序状态和行为。断点一旦设置便保留在你的项目中,直到你明确删除它。

F10

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

F11

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

CTRL + F5

开始运行不调试,直接让程序运行起来。如果不想进行调试可以直接使用此快捷键运行程序。

查看更多快捷键

3.4.查看当前程序的信息

3.4.1.查看当前临时程序变量的值

在这里插入图片描述
在调试时,观察变量的值的变化。
在这里插入图片描述

3.4.2.查看内存信息

在这里插入图片描述

在这里插入图片描述
通过&取地址操作符,找到变量存放的位置以及存储在内存中的值。
在这里插入图片描述

3.4.3.查看调用堆栈

在这里插入图片描述
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。
在这里插入图片描述

3.4.4.查看汇编信息

在调试开始之后,有两种方式转到汇编:
方式一:右击鼠标,选择【转到反汇编】:
在这里插入图片描述
方法二:在调试菜单中的窗口菜单中:
在这里插入图片描述
在这里插入图片描述

3.4.5.查看寄存器信息

在这里插入图片描述
查看当前的寄存器信息
在这里插入图片描述

4.调试问题代码

4.1.例一


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

为什么统计的各个阶乘的和是错的呢?
我们通过调试来看看
在这里插入图片描述
在这里插入图片描述
通过调试我们发现,当i = 3时,此时ret的值为2,所以才导致求3的阶乘时程序产生了bug。找到了问题,下面是分析该问题,这样每次统计完一个阶乘并把它放入累加的变量中,进入下一次循环前,要把ret的值修改为1,以确保求出阶乘是 正确的。故修改后代码如下

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;//每次求n的阶乘将ret的值改成1
		for (j = 1; j <= i; j++)
		{
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

在这里插入图片描述

4.2.调试实例例二

//为什么死循环打印hehe?

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

数组越界访问后,这里程序为什么死循环打印hehe呢?调试一下你就知道。
在这里插入图片描述
通过监视发现,arr[12]的值随着 i 的值发生变化。
在这里插入图片描述
根据查看内存中的地址,可以发现,arr[12]的地址和i的地址是一样的。所以当i = 12进入循环,执行arr[i] = 0;后,i 的值又变成0。循环往复造成程序死循环输出hehe。需要注意的是:这段代码仅在VS2019 X86环境有效。下面我通过画图的方式来演示一下这段代码在内存中的情况。
在这里插入图片描述

越界访问会造成程序的错误,我们应该在使用数组的时候检查下标避免越界访问。

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

5.1.什么是优秀的代码?

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

编写优秀代码技巧

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

5.2.优秀编码实例

//模拟实现库函数strlen
size_t my_strlen(const char* str)
{
	//断言判断指针有效性
	assert(str != NULL);
	//记录起始位置
	char* start = str;
	while(*str++)//str指向的内容不为\0指针++
	{
		;
	}
	//通过指针减指针求出字符长度
	return str - start - 1;
}

补充:

1、使用断言assert需要包含对应头文件 #include<assert.h>.
2、表达式应注意操作符的优先级问题。
3、通过const来修饰形式参数,保证指针的安全性
4、确定合适的返回类型。因为字符串长度不可能是负数,所以返回类型定义为size_t
5、添加合适的注释

5.3.const修饰指针变量

//const放在*号左边
int main()
{
	int num = 10;
	int n = 1000;
	const int* p = &num;
	*p = 20;//err
	p = &n;//ok
	return 0;
}

当const放在 * 号左边时,修饰的是指针变量指向的内容,指针变量指向的内容不可以被修改,但是指针变量本身可以被修改。

//const放在 *号右边
int main()
{
	int num = 10;
	int n = 1000;
	int* const p = &num;
	*p = 20;//ok
	p = &n;//err
	return 0;
}

当const放在 * 号右边时,修饰的是指针变量本身,指针变量不可以被修改,但是指针变量指向的内容可以被修改。

总结

只有多多动手调试,才能够有进步!当遇到bug时,不要再迷信式的调试。我们要尝试通过调试来验证我们的想法是否能够实现。也只有加化自身的调试技巧,才能够让编程之路越走越远。

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

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

相关文章

什么是混淆矩阵精度、召回率、准确性、F1 分数、FPR、FNR、TPR、TNR?

在你的数据科学生涯的开始,混淆矩阵会非常混乱,我们会有很多问题,比如什么时候使用精度?什么时候使用召回?在哪些情况下可以使用精度?因此,我将尝试在本博客中回答这些问题。 什么是混淆矩阵? 混淆矩阵是一种将预测结果和实际值以矩阵形式汇总的方法,用来衡量分类问题…

带头双向循环链表的实现

目录前言节点声明链表的初始化尾插打印链表头插尾删头删查找节点指定位置插入指定位置删除链表销毁前言 之前讲过单链表的实现&#xff0c;在实现的过程中&#xff0c;我们会发现每次删除或者在前面插入节点的时候&#xff0c;都要提前保存上一个节点的地址。这样做十分麻烦&a…

大一新生HTML期末作业个人介绍博客 使用html+css+javascript+jquery技术制作网页,含有动画,hover效果,含有表格布局

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

RV1126/RV1109 IPC板 + RK3568+鸿蒙AI视频解决方案

近年来&#xff0c;云终端产品在办公、教育、工控等行业被广泛应用&#xff0c;其具有实用性强、运维量小、数据存储更安全等特点&#xff0c;深受市场青睐。而面对复杂光照环境、人流与车流、多变人体动作等复杂场景&#xff0c;成像质量和画面效果以及细节呈现能力&#xff0…

Ansible自动化运维工具之playbook剧本编写(上)

内容预知 1.playbook的相关知识 1.1 playbook 的简介 1.2 playbook的 各部分组成 2. 基础的playbook剧本编写实例 实例1&#xff1a;playbook编写 apache的yum安装部署剧本 实例2&#xff1a;playbook编写nginx 的yum安装并且能修改其监听端口的剧本 3. playbook的定义、引…

网站如何快速变成灰色?,几行代码就搞定了!

当大家看到全站的内容都变成了灰色&#xff0c;包括按钮、图片等等。这时候我们可能会好奇这是怎么做到的呢&#xff1f;有人会以为所有的内容都统一换了一个 CSS 样式&#xff0c;图片也全换成灰色的了&#xff0c;按钮等样式也统一换成了灰色样式。但你想想这个成本也太高了&…

ThreadLocal笔记

并发的场景中&#xff0c;如果有多个线程同时修改公共变量&#xff0c;可能会出现线程安全问题&#xff0c;即该变量最终结果可能出现异常。 如果使用锁来保证资源隔离&#xff0c;会存在大量锁等待&#xff0c;会让响应时间延长很多。 ThreadLocal的核心思想是&#xff1a;共享…

云服务器centos8搭建网站 apache+php+mysql

由于对数据库容量要求比较大&#xff0c;年费用300左右的普通虚拟主机只能提供500M-1G的数据库&#xff0c;不能满足要求&#xff0c;故寻找到同样费用的云服务器单核、1G内存、系统盘50G&#xff0c;缺点是只提供基本系统centos&#xff0c;其他要自己搭建&#xff0c;经过一周…

05_openstack之Neutron网络管理

目录 一、环境准备 二、通过Horizon设置外部网络 1、创建外网网络 2、创建内网网络 3、创建路由 一、环境准备 部署openstack私有云环境&#xff1a;02_openstack私有云部署_桂安俊kylinOS的博客-CSDN博客 创建项目和用户&#xff1a;03_openstack之项目及用户管理_桂安…

fastTEXT论文解读并附实例代码

上一篇博文是入门使用级别&#xff0c;但对于面试来说则不够&#xff0c;毕竟领导一问三不知必定over&#xff0c;其基本原理还是要搞清楚&#xff0c;因而有此博文。paper在此 0&#xff0c;绪论 考虑紧致特征以减少存储空间&#xff0c;提出在PQ&#xff08; product quant…

三、CANdelaStudio入门-视图类型(View type)

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio的各个视图类型:Standard View、Expert V…

Jmeter的使用教程(安装)

前言&#xff1a;之前在工作中未接触过Jmeter&#xff0c;只是知道这个Java语言开发的工具主要是测试接口的&#xff0c;还能做一些性能和压力的测试、并发什么的。目前市面上的有postman、apifox&#xff0c;request、swagger。 一、下载&#xff1a; 1、可以直接到官网下载&a…

(Cascade extended state observer)级联ADRC的simulink仿真和程序---送给中国研究学者的精华版

在这里先声明一下&#xff0c;级联CESO由美国学者Rafal Madonski的论文 《Cascade extended state observer for active disturbance rejection control applications under measurement noise》提出&#xff0c;本人只是将他给的模型给中国学者研究&#xff0c; 模型适用于各…

做了几年“斜杠青年”,我在ZStack立志做国产云计算的研发

在浅黑科技《ZStack&#xff1a;这群做云的人有点“轴”》一文中&#xff0c;作者史中提到&#xff0c;这是一篇国产云计算佼佼者ZStack的创业史&#xff0c;文中记录了因为热爱而聚集起来的最早一批ZStacker&#xff0c;他们生活没有退路&#xff0c;但热爱未有止息。 实际上…

概率论与数理统计_第1章_几何概型

1 定义 若一个试验具有下列两个特征&#xff1a; &#xff08;1&#xff09;试验的所有可能结果是无限多个&#xff0c; 且全体结果可以用一个有度量的几何区域 Ω 来表示&#xff1b; &#xff08;2&#xff09;每个可能结果 都相同概率可能发生&#xff0c; 则该试验称为几何…

Pytorch:Torch数据类型学习整理与记录

文章目录前言一、Tensor数据类型简介Tensor数据类型是什么?Tensor数据类型有哪些指定调用的API生成相关数据类型dtype属性指定Tensor内置的简单数据类型二、Tensor数据类型的基本使用Tensor初始化基于list列表和nparrayTensor相关API基于指定Tensor类型进行初始化基于Randn生成…

postgres源码解析40 表创建执行全流程梳理--4

本文讲解非系统表的创建逻辑&#xff08;[<fontcolor0000dd>普通表和索引表]&#xff09;&#xff0c;其入口函数为heap_create&#xff0c;内部公共接口函数为RelationBuildLocalRelation和RelationCreateStorage相关知识回顾见&#xff1a; postgres源码解析38 表创建执…

Mac M1使用UTM安装centos7 x86_64虚拟机

一、环境说明 1. 宿主机环境 macbook m1 pro 16G 2. UTM版本 UTM是基于QEMU的系统模拟器和虚拟机主机&#xff0c;适用于iOS和macOS。 UTM is a full featured system emulator and virtual machine host for iOS and macOS. It is based off of QEMU. 最新版下载地址&…

带你玩转序列模型之NLP与词嵌入(二)

目录 一.Word2Vec 二.负采样 三.GloVe词向量 四.情绪分类 五.词嵌入除偏 一.Word2Vec 在上个视频中你已经见到了如何学习一个神经语言模型来得到更好的词嵌入&#xff0c;在本视频中你会见到 Word2Vec算法&#xff0c;这是一种简单而且计算时更加高效的方式来学习这种类…

用于 Python 降维的主成分分析

减少预测模型的输入变量数称为降维。 较少的输入变量可以产生更简单的预测模型&#xff0c;该模型在对新数据进行预测时可能具有更好的性能。 也许机器学习中最流行的降维技术是主成分分析&#xff0c;简称PCA。这是一种来自线性代数领域的技术&#xff0c;可用作数据准备技术…