实用调试技巧
目录:
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:数字签名、数字证书、信任关系 身份认…...

flutter 启动其他app server或者页面失败
1.目标Service 设置 android:exported"true" 2.目标Service需要声明自定义权限。客户端需要声明权限。 3.目标Service需要添加<intent-filter></intent-filter> 检查以上的声明和权限, 如果还是不行 说明是 Android 11引入了*包可见性*’ …...

【linux-进程2】进程控制
🌈环境变量 🍄初识 系统带的命令可以直接运行(ls ll命令等),但是我们自己写的命令必须要带上路径才能运行(./myproc),这是什么原因导致的?如果我们也想自己写的命令直接…...

【五一创作】多域名环境和Office 365混合部署方案
目录 一、多域名环境是什么? 二、Office 365是什么? 三、多域名环境与Office 365的结合 总结 一、多域名环境是什么? 多域名环境指的是一个企业拥有多个域名,这些域名可能隶属于不同的子公司、部门或者品牌,但是都归属于同一个母公司。例如,一个中国电信集团旗下有…...

Vue:路由route
一、概念 1、组成 每一个路由都由 key 和 value 组成。 keyvalue路由 route。 2、本质 路由的本质:一个路由表达了一组对应关系。路由器的本质:管理多组对应关系。 3、路由的工作原理 点击之后路径变化——>路由器监视到变化——>根据路径…...

Windows系统被faust勒索病毒攻击勒索病毒解密服务器与数据库解密恢复
在近期,一种名为faust后缀的勒索病毒威胁已经引起了全球计算机系统安全领域的关注。faust勒索病毒是一种基于RSA加密算法的恶意软件,能够加密目标计算机系统上的所有文件,并向用户勒索赎金来承诺解密恢复操作。下面为大家介绍一下Windows系统…...

Java面试题总结 | Java面试题总结7- Redis模块(持续更新)
Redis 文章目录 Redisredis的线程模型Redis的Mysql的区别Redis和传统的关系型数据库有什么不同?Redis常见的数据结构zset数据结构Redis中rehash过程redis为什么不考虑线程安全的问题呢Redis单线程为什么还能这么快?为什么Redis是单线程的?red…...

虹科案例 | 如何通过智能、非接触式测量解决方案,提高起重机的安全和效率?
PART 1 案例详情 自建造初期以来,起重机行业已经走了很长一段路。技术的使用在行业进步中发挥了重要作用,降低了使用桥式起重机的危险性。特别是,智能、非接触式测量解决方案通过使用高架升降机更安全、更高效、更高效,为行业的进…...

流程图拖拽视觉编程-流程编辑器
目录 一、简介 二、流程编辑器-视图实现 三、参考资料 一、简介 前期文章: 流程图拖拽视觉编程--概述_Jason~shen的博客-CSDN博客 本期内容: 本期将介绍流程编辑器模块的实现方法,效果图如下所示。该模块基于QT Graphics/View实现&…...

6.hashcode与equals区别与联系
1.hashCode介绍 hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。 这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 2.equals介…...

智能家居“落地者”:三翼鸟用场景方案持续链接大众消费
互联网分析沙龙(techxue)原创 作者 | 锡海 编辑 | 七喜 从上海车展再到AWE2023展会,只要有大型活动的地方,都能看到人潮汹涌的景象,久违的烟火气又回来了。数据显示,社会消费已出现较为强劲反弹࿰…...