实用调试技巧
目录:
1.什么是bug?
2.调试是什么?有多重要?
3.debug和release的介绍
4.Windows环境调试介绍
5.一些调试的实例
6.如何写出好(易于调试)的代码
7.编程常见的错误
1.什么是bug?
bug--->臭虫、虫子。
为什么含义是臭虫、虫子呢?
答案是:第一次被发现的导致计算机错误的是一只飞蛾,也是第一个计算机程序的错误。
2.调试是什么?有多重要?
前言:
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。
顺着这条途径顺流而下就是犯罪,逆流而上就是真相。
一名优秀的程序员都是一名出色的侦探。
每一次调试都是尝试破案的过程。
2.1调试是什么?
调试(英语:Debugging/Debug),又称除错,是发现和减少计算机或电子仪器设备中程序错误的一个过程。
2.2调试的基本步骤
①发现程序错误的存在
②以隔离、消除等方式对错误进行定位
③确定错误产生的原因
④提出纠正错误的解决办法
⑤对程序错误予以改正,重新测试
2.3Debeg和Release的介绍
Debug通常称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序。
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
(1)在VS2019中如何转换?
(2)Release对代码大小上是最优的:
代码:
#include<stdio.h>//自定义函数——实现对整数数组的冒泡排序
void bubble_sort(int* str, int sz)
{//趟数int i = 0;for (i = 0; i < sz - 1; i++){//一趟冒泡排序的过程int j = 0;for (j = 0; j < sz - 1 - i; j++){//升序if (str[j] > str[j + 1]){int tmp = str[j];str[j] = str[j + 1];str[j + 1] = tmp;}}}
}int main()
{int arr[] = { 10,9,8,7,5,6,4,1,2,3 };//定义整形数组,并初始化int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的大小//调用函数,实现升序bubble_sort(arr, sz);//输出升序后的数组int i = 0;//循环变量for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
观察分别在Debeg和Release环境下生成的可执行程序的大小:
(3)Release对代码运行速度上是最优的
代码:
#include <stdio.h>
int main()
{int i = 0;//数组下标界限0~9int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };for (i = 0; i <= 12; i++){//数组下标为10~12时数组越界arr[i] = 0;printf("hehe\n");}return 0;
}
在Debeg和环境下该代码死循环,解释在调试的实例二。
在Release环境下该代码打印13次hehe,不死循环,因为编译器优化把i的地址放在数组arr地址的下面了。
3.Windows环境调试介绍
3.1调试环境的准备
在环境中选择debug选项,才能使代码正常调试。
3.2学会快捷键
最常使用的几个快捷键:
F5
启动调试,经常用来直接跳到下一个断点处。
F9
创建断点和取消断点。
断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程序在想要的位置停止执行,继而一步步执行下来。
F5和F9常配合使用,F5一般不会单独使用的。断点再多文件、多代码中常用。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)。
ctrl + F5
开始执行不调试。如果你想让程序直接运行起来而不调试就可以直接使用。
3.3调试的时候查看程序当前信息
注意:只有先F10开始调试,才能看到程序当前信息。
3.3.1查看临时变量的值
在调试开始之后,用于观察变量的值。

3.3.2查看内存信息
在F10调试开始之后,用于观察内存信息。

3.3.3查看汇编信息
在F10调试开始之后,有两种方式转到汇编。
(1)第一种方式:右击鼠标,选择[转到汇编]:
(2)第二种方式:
可以切换到汇编。
3.3.4查看寄存器信息
在F10调试起来之后,有两种方式观察寄存器信息。
(1)第一种方式:
(2)知道寄存器的名字,可以在监视中观察寄存器信息。
可以查看当前运行环境的寄存器的使用信息。
3.3.5查看调用堆栈
在F10调试之后,可以观察调用堆栈。
通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。
4.多多动手,尝试调试,才能有进步
①一定要熟练掌握调试技巧;
②初学者可能80%的时间在写代码,20%的时间在调试。但是一个程序员可能20%的时间在写代码,但是80%的时间在调试。
③我们现在所讲的都是一些简单的调试,以后可能会出现很复杂的调试场景:多线程程序的调试等。
④多多使用快捷键,提升效率。
5.一些调试的实例
实例一:

代码1:实现阶乘
#include<stdio.h>int main()
{//输入求几的阶乘int n = 0;scanf("%d", &n);//实现求n! n!=n*(n-1)int ret = 0;int i = 0;for (i = 1; i <= n; i++){ret *= i;}//输出结果printf("%d\n", ret);return 0;
}
如果我们输入3,想输出6,但实际输出的是0.
why?
这里我们就得找我们的问题:
①首先通过经验推测问题出现的原因,初步确定问题可能的原因最好。
②实际上手调试很有必要。
③调试的时候我们要心里有数。
通过初步推测ret变量有问题,我们在在for循环打断点调试观察变量ret具体有什么问题。
代码2: 求 1!+2!+3! ...+ n!
#include<stdio.h>int main()
{//输入有n个阶乘int n = 0;scanf("%d", &n);//循环 求 1!+2!+3! ...+ n! int ret = 1;int i = 0;int sum = 0;//存放阶乘的累加和for (i = 1; i <= n; i++){int j = 0;//实现求i的阶乘for (j = 1; j <= i; j++){ret *= j;}sum += ret;}//输出结果printf("%d\n", sum);return 0;
}
如果我们输入3,想输出9,但实际输出15。
why?
分析:推测循环出错了,第一次调试在第二个循环处打断点,一步步调试监视变量的变化。
但是没有发现是哪里错了,第二次调试在断点处右击设置断点条件快速调试到错误处,符合断点条件就停止,再F10观察具体原因。
实例二:
#include <stdio.h>
int main()
{int i = 0;//数组下标界限0~9int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };for (i = 0; i <= 12; i++){//数组下标为10~12时数组越界arr[i] = 0;printf("hehe\n");}return 0;
}
数组越界应该是程序错误,不执行但是我们运行后发现程序死循环了。
why?
我们F10调试起来观察变量。
在调试的时候,我们发现每一次i的值都和arr[12]的值一样,当arr[12]=0时,i也变成0了,所以死循环。
那arr[12]和i是不是地址一样?我们调试观察之后确实是一样的。
图解:
在i和arr数组中间恰好就是2个整形吗?
答:不一定,该代码只是在VS2019 X86环境下实验的结果。
如果是VC6.0——i和arr之间没有多余的空间,gcc——i和arr之间有一个整形空间。
所以说平时我们写代码要注意不数组越界了。
6.如何写出好(易于调试)的代码
6.1优秀的代码:
①代码运行正常
②bug很少
③效率高
④可读性高(如良好的代码风格,函数名、变量名见名知意等)
⑤可维护性高
⑥注释清晰
⑦文档齐全
常见的coding技巧:
①使用assert
②尽量使用const
③养成良好的编码风格
④添加必要的注释
⑤避免编码的陷阱
6.2示范
模拟实现库函数strcpy:
strcpy:
1.函数原型:
2.函数功能:
3.函数参数:
4.函数的返回类型:
代码1:模拟实现strcpy
分析:
#include<stdio.h>
#include<string.h>//自定义strcpy//代码1
void my_strcpy(char* dest, const char* src)
{//拷贝'\0'之前的字符while (*src != '\0'){*dest = *src;dest++;src++;}//拷贝'\0'*dest = *src;
}
int main()
{//将arr2中的字符串拷贝在arr1char arr1[20] = "#############";char arr2[] = "hello";//调用库函数实现//strcpy(arr1, arr2);//调用自定义函数实现my_strcpy(arr1, arr2);//打印拷贝后的arr1printf("%s\n", arr1);return 0;
}
代码2:优化函数体
#include<stdio.h>
#include<assert.h>//自定义strcpy//代码2
void my_strcpy(char* dest, const char* src)
{//优化1:使用指针之前一定要检查是否有效,如果无效就报错//assert--断言// assert中可以放一个表达式,表达式结果为假就报错,为真就啥事都不发生,正常运行//assert的头文件是assert.h//assert其实在release版本中被优化调了assert(dest && src);//断言指针的有效性//优化2:使代码简化//*dest++ = *src++;//等价于//*dest = *src;//dest++;src++;while (*dest++ = *src++)//'\0'的ASCII码值就是0,所以拷贝到'\0'停止{;}
}
int main()
{//将p中的字符串拷贝在arr1char arr1[20] = "#############";char* p = NULL;//调用自定义函数实现my_strcpy(arr1, p);//打印拷贝后的arr1printf("%s\n", arr1);return 0;
}
程序结果:
代码3:优化函数的形参
如下代码我们程序不报错,但是没有成功完成我们想要的拷贝:
#include<stdio.h>
#include<assert.h>void my_strcpy(char* dest, char* src)
{assert(dest && src);//断言指针的有效性while(*src++ = *dest++)//程序员喝酒,写反了这样我们没有实现拷贝的目的{;}//将src所指向内容拷贝到dest所指向数组
}int main()
{char arr[20] = "#############";char arr1[20] = "hello";my_strcpy(arr, arr1);printf("%s\n", arr);return 0;
}
该怎么避免出现这种错误呢?
我们先来学习const的作用:
#include <stdio.h>void test()
{//代码1//定义两个整型变量int n = 10;int m = 20;//没有const修饰int* p = &n;//可以通过指针变量p将指针所指向的内容n的值改成20?*p = 20;//ok//可以修改指针变量本身?p = &m; //ok
}
void test1()
{//代码2const int num = 10;//num = 20;//err,因为num被const修饰,所以不能修改//但是通过指针变量p,num能被修改了(p就像卖票的黄牛一样)int* p = #*p = 20;
}
void test2()
{//代码3int n = 10;int m = 20;//const放在*的左边const int* p = &n;//也可写成:int const* p = &n;//*p = 20;//err,因为const修饰的指针p指向的内容,所以不能通过指针来修改p = &m; //ok,因为const只修饰的是指针p指向的内容,所以指针变量本身可以修改
}
void test3()
{//代码4int n = 10;int m = 20;//const放在*的右边int* const p = &n;*p = 20; //ok,因为const只修饰的是指针变量本身,所以指针指向的内容可以通过指针改变//p = &m; //err,因为const修饰的是指针变量本身,所以指针变量本身不能被修改
}
int main()
{//测试无cosnt的test();//测试const修饰变量test1();//测试const放在*的左边test2();//测试const放在*的右边test3();return 0;
}
结论:
const修饰指针变量的时候:
1.const如果放在*的左边,const修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变;但是指针变量本身可以修改。
2.const如果放在*的右边,const修饰的是指针变量本身,保证指针变量本身的内容不能被修改;但是指针指向的内容,可以通过指针来改变。
3.const就像法律,不能被修改。
学习了const的作用,我们来修改刚在代码的问题,可以运行但是没有完成拷贝,是因为*dest++和*src++写反了,因为src所指向的内容不变。所以我们可以在把第二个形参改成const int* src,用const修饰指针指向的内容,这样的话如果不小心将*dest++和*src++写反直接就编译错误,不会运行成功,很快就发现代码的错误了。
#include<stdio.h>
#include<assert.h>void my_strcpy(char* dest,const char* src)
{assert(dest && src);//断言指针的有效性while (*src++ = *dest++)//程序员喝酒,写反了这样我们没有实现拷贝的目的{;}//将src所指向内容拷贝到dest所指向数组
}int main()
{char arr[20] = "#############";char arr1[20] = "hello";my_strcpy(arr, arr1);printf("%s\n", arr);return 0;
}
如下图:
代码4:优化函数的返回类型(最终的优化版本)
#include<stdio.h>
#include<assert.h>//库函数strcpy的返回值是目的地的起始地址
char* my_strcpy(char* dest,const char* src)
{assert(dest && src);//断言指针的有效性char* ret = dest;//存放目的地的起始地址while (*dest++ = *src++){;}//将src所指向内容拷贝到dest所指向数组return ret;
}int main()
{char arr[20] = "#############";char arr1[20] = "hello";//优点:链式访问(有返回值才可以)printf("%s\n", my_strcpy(arr, arr1));return 0;
}
运行结果:
练习:模拟strlen
#include<stdio.h>
#include<assert.h>//size_t是unsigned int的别名,因为长度没有负数
size_t my_strlen(const char* str)
{assert(str != NULL);//断言指针的有效性size_t count = 0;//计数while (*str++){count++;}return count;
}int main()
{char arr[] = "abcdef";printf("%d\n", my_strlen(arr));return 0;
}
7.编程常见的错误
7.1编译型错误(语法错误)
直接看错误提示信息(双击锁定),解决问题。或者凭借经验就可以搞定,相对来说简单。
7.2链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。
类型1:库函数不包含头文件
类型2:拼写错误
我们怎么找到错误位置?
7.3运行时错误(编译、链接都没错,但是运行结果有问题)
借助调试,逐步定位问题,最难搞。
最后温馨提示:
做一个有心人,积累排错经验!
相关文章:

实用调试技巧
目录: 1.什么是bug? 2.调试是什么?有多重要? 3.debug和release的介绍 4.Windows环境调试介绍 5.一些调试的实例 6.如何写出好(易于调试)的代码 7.编程常见的错误 1.什么是bug? bug--->臭虫、虫子。 为什么含…...

谁是液冷行业真龙头?疯狂的液冷技术!
“人工智能领域AIGC”、“ChatGPT”、“数据特区”、“东数西算”、“数据中心”,可以说是2023年最热的概念,算力提升的背后,处理器的功耗越来越高,想发挥出处理器的最高性能,需要更高的散热效率。 算力井喷之下&…...

自动化运维工具之Ansible
目录 一、自动化运维 1、通过xshell自动化运维 2、Ansible简介 3、Ansible特点及优势 4、Ansible核心程序 5、Ansible工作原理及流程 6、部署Ansible自动化运维工具 7、Ansible常用模块 (1) ansible命令行模块 (2) command模块 (3) shell模块 (4) cron模块 (5) us…...

霍兰德人格分析雷达图
雷达图 Radar Chart 雷达图是多特性直观展示的重要方式 问题分析 霍兰德认为:人格兴趣与职业之间应有一种内在的对应关系 人格分类:研究型、艺术型、社会型、企业型、传统型、现实性 职业:工程师、实验员、艺术家、推销员、记事员、社会工…...

《Odoo开发者模式必知必会》—— 缘起
Odoo作为业界优秀的开源商务软件,在全球范围内拥有广泛的使用者。在领英国际,可以搜索到全球很多国家都有大量odoo人才需求的招聘信息。在国内,虽然已经有为数不少的企业,他们或者已经使用odoo,或者正在了解odoo&#…...
Java8的Options介绍
Java8引入了一个名为 Options 的新类,它是一个容器,可以保存单个值或根本不保存任何值。Optional目的是提供一种更优雅的方式来处理 null 值,这通常会导致NullPointerException。在这篇博客文章中,我们将探索如何在 Java8中使用 O…...

SpringBoot 多数据源及事务解决方案
1. 背景 一个主库和N个应用库的数据源,并且会同时操作主库和应用库的数据,需要解决以下两个问题: 如何动态管理多个数据源以及切换? 如何保证多数据源场景下的数据一致性(事务)? 本文主要探讨这两个问题的解决方案…...

tcpdump使用教程
一、概述 tcpdump是一个功能强大的,用于抓取网络数据包的命令行工具,与带界面的Wireshark一样,基于libpcap库构建。这篇文章主要介绍tcpdump的使用。关于如何使用tcpdump的资料中,最有用的就是tcpdump的两个手册。 tcpdump使用手…...

Zynq-7000、FMQL45T900的GPIO控制(五)---linux应用层配置GPIO输出控制
上文中详细阐述了对应原理图MIO/EMIO的编号,怎么计算获取linux下gpio的编号 本文涉及C代码上传,下载地址 Zynq-7000、FMQL45T900的GPIO控制c语言代码资源-CSDN文库 本文详细记录一下针对获取到gpio的编号,进行配置输出模式,并进…...

带你搞懂人工智能、机器学习和深度学习!
不少高校的小伙伴找我聊入门人工智能该怎么起步,如何快速入门,多长时间能成长为中高级工程师(聊下来感觉大多数学生党就是焦虑,毕业即失业,尤其现在就业环境这么差),但聊到最后,很多…...
Android 11.0 framework中Launcher的启动流程分析
1.前言 在11.0的系统rom定制化开发中,在rom定制过程中,在对于开发默认Launcher功能,解决开机动画后黑屏,了解fallbackhome机制等等 对于launcher的启动流程来说很重要,接下来就来分析下launcher的启动流程 2.framework中Launcher的启动流程分析的核心类 frameworks/ba…...

2023年第十五届华中杯赛题C 题 空气质量预测与预警
2023年五一假期期间,数学建模竞赛就有四场,各种比赛各种需求应接不暇。因此,对于本次浅析有不足的地方欢迎大家指出。为了更好的帮助大家华中杯参赛,下面带来,C题详细版思路。由于C题的难度,注定选题人数将…...
Go官方指南(一)包、变量、函数
import "time" 获取当前系统时间:time.Now() 每个 Go 程序都是由包构成的 按照约定 ,包名与导入路径的最后一个元素一致。例如,"math/rand"包中的源码均以 package rand 语句开始 在 Go 中,如果一个名字以…...

liunx笔记
快捷键 #移动到行首 ctrla #移动到行尾 ctrle #删除光标之前的字符 ctrlu #删除光标之后的字符 ctrlk #清屏 ctrll正则表达式 正则中普通常用的元字符 元字符功能.匹配除了换行符以外的任意单个字符*前导字符出现0次或连续多次.*任意长度字符^行首(以…开头),如…...

vue3 封装ECharts组件
一、前言 前端开发需要经常使用ECharts图表渲染数据信息,在一个项目中我们经常需要使用多个图表,选择封装ECharts组件复用的方式可以减少代码量,增加开发效率。 ECharts图表大家应该用的都比较多,基础的用法就不细说了ÿ…...
Spring Security 6.0系列【30】授权服务器篇之JOSE规范
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 本系列Spring Authorization Server 版本 1.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 1. 前言2. JOSE 规范3. JW…...
维度表设计原则
维度的作用一般是查询约束、分类汇总以及排序等,我们在进行维度表设计时,应当提前考虑: (1)维度属性尽量丰富,为数据使用打下基础 比如淘宝商品维度有近百个维度属性,为下游的数据统计、分析、…...

【requests模块上】——02爬虫基础——如桃花来
目录索引 requests请求:1. 基于get请求:*基础写法:**带参数的get请求:* 2. 基于post请求: 获取数据:1. 获取json数据:2. 获取二进制数据: 初步伪装小爬虫——添加headers: 引入&…...

Springboot +Flowable,详细解释啥叫流程实例(一)
一.简介 上一篇中学习了Flowable 中的流程模板(流程定义)的部署问题,这一篇来学习什么叫流程实例。 部署之后的流程模板,还不能直接运行,例如我们部署了一个请假流程,现在 张三想要请假,他就需…...

信息安全复习十:Web与电子商务安全
一、章节梗概 1.信息安全的学科内容 2.Web和电子商务安全问题提出 3.安全套接字协议SSL与传输层安全协议TLS 4.安全电子交易(SET)简要介绍 复习: 密码学内容:对称密钥密码、公开密钥密码、报文鉴别 PKI:数字签名、数字证书、信任关系 身份认…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...