实用调试技巧

文章目录

  • 实用调试技巧
    • 什么是bug?
    • 调试是什么?有多重要?
    • Debug和Release的介绍
    • windows环境调试介绍
    • 一些调试的实例
    • 如何写出好的代码
    • const
  • 什么是bug?
  • 调试是什么?重要吗?
  • debug和release的介绍
  • windows环境调试介绍
  • 一些调试实例
  • 如何写出好的代码
  • const

什么是bug?

为马克2号(Harvard Mark II)编制程序的葛丽丝·霍波(Grace Hopper)是一位美国海军准将及计算机科学家,同时也是世界最早的一批程序设计师之一,有一天,她在调试设备时出现故障,拆开继电器后,发现有只飞蛾被夹扁在触点中间,从而“卡”住了机器的运行。于是,霍波诙谐的把程序故障统称为BUG(飞虫),把排除程序故障叫DEBUG,而这奇怪的“称呼”,竟成为后来计算机领域的专业行话。这是历史上第一次程序的bug。

调试是什么?有多重要?

你必须掌握的一种重要的技能就是调试。虽然调试经常会有挫败感,但是它仍旧是编程工作中最有趣、有挑战性和有价值的部分。调试就像侦探工作。你掌握的是各种线索,你必须据此猜测导致你所见的结果的过程与事件。调试又像实验科学。一旦你觉得可以猜测到出问题的地方,你会修改你的程序,再次运行。如果你的想法是正确的,那么你可以预测这次修改的结果,这样就向正确的程序迈进了一步;如果是错的,你必须再次提出新的想法。就像夏洛克福尔摩斯说的那样:“当你排除了所有不可能,无论剩下的是什么,不管看起来多么不可能,那都是真相。

一名优秀的程序员是一名出色的侦探


拒绝迷信式调试!!!

试来试去结果运行成功了,但是不知道为什么。

调式的基本步骤

  • 发现程序错误的存在

    1.程序员自己发现并解决

    2.软件测试人员 - 测试软件

    3.用户发现问题

  • 以隔离、消除等方式对错误进行定位

  • 确定错误产生的原因

  • 提出纠正错误的解决办法

  • 对程序错误予以改正,重新测试

Debug和Release的介绍

Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release称为发布版本,它往往进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

那他们两有什么区别呢?看个例子

int main()
{int arr[10]={0};int i=0;for(i=0;i<10;i++){arr[i]=i;}return 0;
}

Debug版本我们可以通过调试一步一步的来观察监视程序

而当我们换成Release版本时:

这时就不能进行调试了

我们在分别运行Debug版本和Realease版本时能在文件路径找到Debug文件和Realease文件

我们分别进去Debug文件和Release文件里面有个可执行程序文件(.exe)

仔细对比发现,两个的大小不一样,是因为Debug版本我们含有调试信息,不作任何优化;而Release不含有调试信息,还会进行优化,大小更小一些。

windows环境调试介绍

1.调试环境的准备

确保环境切换为Debug

2.学会快捷键

常用快捷键

  • F5

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


  • F9

创建断点和取消断点 断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程序在
想要的位置随意停止执行,继而一步步执行下去。


  • F10

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


  • F11

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

3.调试的时候查看程序当前信息

查看临时变量的值

查看内存信息

查看汇编信息

查看调用堆栈

通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置。前面的是被后面的调用的。

查看寄存器

一些调试的实例

实例一:

求1!+2!+3!+4!+5!

int main()
{int i=0;int j=0;int ret=1;int n=0;int sum=0;scanf("%d",n);for(i=1;i<=n;i++){for(j=1;j<i;j++){ret*=j;}sum+=ret;}return 0;
}

1!+2!+3!这个代码有问题,如果我们实在想不明白,我们进行调试

通过一步一步认真调试监视发现,ret没有重置为1,所以结果会有错误

int main()
{int i=0;int j=0;int ret=1;int n=0;int sum=0;scanf("%d",n);for(i=1;i<=n;i++){for(j=1;j<i;j++){ret*=j;}sum+=ret;ret=1;}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("haha\n");}return 0;
}

运行结果是啥?为什么?

运行后会进入死循环打印haha,数组明明越界了,为什么不报错呢?是因为它陷进了死循环,停不下来,所以不会报错。

我们进行调试,调试后发现arr[12]恰好就是i,改变arr[12]时也改变i,所以进行了死循环。

原因如图解释所示:

i和arr是局部变量,局部变量是放在栈区上的
栈区的使用习惯是:先使用高地址空间,再使用低地址。

我们在将程序换成release版本运行

程序会停下来,那是因为release版本是对代码进行优化过的,代码大小和运行速度都是最优的,这里在release版本下,将局部变量i放在了低地址处,看下图

debug版本

i在高地址

release版本

i到了低地址

所以在release版本底下,代码进行了优化,不会死循环

如何写出好的代码

优秀的代码:

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

常见的coding技巧:

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

模拟实现库函数:strcpy - 字符串拷贝

void my_strcpy(char arr2[], char arr1[])
{while (1){*arr2 = *arr1;if (*arr2 =='\0'){break;}arr1++;arr2++;}
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };my_strcpy(arr2,arr1);printf("%s", arr2);return 0;
}
void my_strcpy(char arr2[], char arr1[])
{while (*arr1 != '\0'){*arr2 = *arr1;//hello的拷贝arr1++;arr2++;}*arr2 = *arr1;//拷贝字符'\0'
}
int main()
{char arr1[] = "hello";char arr2[20] = { 0 };my_strcpy(arr2,arr1);printf("%s", arr2);return 0;
}

进行优化

void my_strcpy(char arr2[], char arr1[])
{while (*arr1 != '\0'){*arr2++ = *arr1++;//hello的拷贝}*arr2 = *arr1;//拷贝字符'\0'
}
int main()
{char arr1[] = "hello";char arr2[20] = { 0 };my_strcpy(arr2,arr1);printf("%s", arr2);return 0;
}

可以继续可以优化,下面代码很妙!

void my_strcpy(char arr2[], char arr1[])
{while (*arr2++ = *arr1++)//'0'->0,既拷贝了\0,又使得循环停止{;}
}
int main()
{char arr1[] = "hello";char arr2[20] = { 0 };my_strcpy(arr2,arr1);printf("%s", arr2);return 0;
}

循环判断条件是*arr2++ = *arr1++,首先我们要知道,a=b这个表达式的值是a的值,我们将arr1中的每个字符赋值给arr2中时,其实整个表达式的值在每次循环时的值分别为’h’e’l’l’o’\0’字符的ASSIC码值,当我们把\0赋值到arr2中时,同时整个表达式的值也为0了,所以退出循环。

但是我们发现如果传过去的是空指针,那解引用不是有问题嘛,所以我们继续优化

在这之后我们还可以优化

因为在使用拷贝函数的人传进来个空指针,那我们是不能对一个空指针解引用的,所以我们使用断言

断言

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。

使用断言可以创建更稳定、品质更好且 不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。

//断言
#include<assert.h>
void my_strcpy(char arr2[], char arr1[])
{//if (arr2 == NULL && arr1 == NULL)//{//   return;//}//不好assert(arr2 != NULL);//为假会报错assert(arr1 != NULL);while (*arr2++ = *arr1++){;}
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s", arr2);return 0;
}

当我们传入空指针时,断言会提示你哪里错了,是不是很美妙,所以学习良好的编程习惯是对别人的友好,更是对自己的友好。

最终最优满分代码

我们会发现库函数strcpy,拷贝源头加了const,函数返回类型为char*。

为什么这样做呢,就是因为我们有时候可能会把拷贝源头和拷贝目的地写反,就会报错,因为两个字符串大小不一样的话,就有问题了

加上const时,当我们写反时,会有以下报错

strcpy库函数返回的是目标空间的起始空间

#include<assert.h>
char* my_strcpy(char arr2[], const char arr1[])
{char* ret=arr2;//if (arr2 == NULL && arr1 == NULL)//{//   return;//}//不好assert(arr2 != NULL);//为真会报错assert(arr1 != NULL);while (*arr2++ = *arr1++){;}return arr2;
}
int main()
{char arr1[] = "hello world";char arr2[20] = { 0 };//my_strcpy(arr2, arr1);printf("%s", my_strcpy(arr2, arr1););//函数返回的是目标的起始地址,  这是链式访问return 0;
}

给arr1加上const的原因是,arr1是拷贝源头,是不能改变的

const

const 修饰变量,这个变量就会被称为常变量,不能被修改,但本质上还是变量

int main()
{const int num=10;int *p=&num;//限制*p*p=20;printf("%d\n",num);return 0;
}

我们不想改变num,但是num把地址给了p,p能够把num给改变了,此时如果我们不想以任何方式改变,就要给指针变量加const修饰

const修饰指针

int main()
{const int num=10;int n=20;const int *p=&num;//限制*p//*p=20;不okp=&n;//okprintf("%d\n",num);return 0;
}
int main()
{const int num=10;int n=20;int *const p=&num;//限制*p*p=20;//ok//p=&n;//不okprintf("%d\n",num);return 0;
}
int main()
{const int num=10;int n=20;int const *const p=&num;//限制*p//const 修饰指针的时候//const放在*的左边,表示指针指向的内容(*p)不能通过指针(p)来改变,但是指针本身(p)是可以修改的//const放在*的右边,表示指针指向的内容(*p)能通过指针(p)来改变,但是指针本身(p)是不可以修改的*p=20;//ok//p=&n;//不okprintf("%d\n",num);return 0;
}

总结

const放在*的左边,表示指针指向的内容(*p)不能通过指针(p)来改变,但是指针本身(p)是可以修改的
const放在*的右边,表示指针指向的内容(*p)能通过指针(p)来改变,但是指针本身(p)是不可以修改的*

模拟实现strlen 求字符串长度

#include<stdio.h>
#include<assert.h>
int my_strlen(const char *str)
{assert(str!=NULL);if(*str!=\0){return 1+my_strlen(str+1);}return 0;
}
int main()
{char arr[]="hello";my_strlen(arr);return 0;
}

欢迎大家交流!

实用调试的技巧,VS编译器常用调试详解相关推荐

  1. QT:常用函数详解--常用操作记录(个人笔记)

    QT:常用函数详解(个人笔记) PS:一下内容个人笔记,要求自己看懂,随笔,阅读体验会很差很差! Qt setContentsMargins()函数 函数原型:void QLayout::setCon ...

  2. Linux常用命令详解(两万字超长文)

    Linux常用命令详解 作为一枚程序员,熟练掌握一些常见的linux命令是很有必要的,掌握这些命令能帮助我们更好地管理Linux系统,提高工作效率,并有效地解决各种问题,为了方便自己后续查阅以及帮助不 ...

  3. U-Boot的常用命令详解

    2019独角兽企业重金招聘Python工程师标准>>> U-Boot的常用命令详解 U-Boot 还提供了更加详细的命令帮助,通过 help 命令还可以查看每个命令的参数说明.由于开 ...

  4. STM32 MQTT协议 连接中国移动OneNet服务器 上传接收数据(二)MQTT协议常用报文详解

    STM32 MQTT协议 连接中国移动OneNet服务器 上传接收数据(二)MQTT协议常用报文详解 上一次我们讲了OneNet平台的注册,这次我们来讲一下MQTT的常用报文用法 上一篇地址https ...

  5. 最全整理!Python 操作 Excel 库 xlrd与xlwt 常用操作详解!

    来源/早起Python 在之前的Python办公自动化系列文章中,我们已经相信介绍了openyxl.xlsxwriter等Python操作Excel库. 相信大家对于几个库的差异与使用场景有了一定的认 ...

  6. [go]Go语言编译器的 “//go:“ 详解

    完全转载,都没排版:原文: 原文地址 别人的简书转载 前言 C 语言的 #include 一上来不太好说明白 Go 语言里 //go: 是什么,我们先来看下非常简单,也是几乎每个写代码的人都知道的东西 ...

  7. Linux常用命令详解文库

     Linux常用命令详解文库.txt精神失常的疯子不可怕,可怕的是精神正常的疯子!Linux常用命令详解 来源: LUPA开源社区 发布时间: 2007-05-27 05:34 版权申明 字体: ...

  8. 单片机c语言常用的语句有几条,单片机C语言常用语句详解

    <单片机C语言常用语句详解>由会员分享,可在线阅读,更多相关<单片机C语言常用语句详解(22页珍藏版)>请在人人文库网上搜索. 1.C51编程中常见语句的总结.首先,C51定义 ...

  9. Rollup常用插件详解

    文章目录 系列文章 @rollup/plugin-node-resolve Options extensions @rollup/plugin-commonjs @rollup/plugin-babe ...

  10. Linux常用命令详解(转)

    Linux常用命令详解(转) Linux之所以受到广大计算机爱好者的喜爱,主要原因有两个,首先它是自由软件,用户不用支付费用就可以使用它,并可根据自己的需要对它进行修改.另外,它具有Unix的全部功能 ...

最新文章

  1. 【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )
  2. 启明云端分享 | 小明带你用一组图查看ESP32-S3 \ESP32-S2\ ESP32的区别
  3. 检测同心圆_(二)光线如何被眼睛检测到?
  4. mysql写入 cpu飙升_分析MySQL中索引引引发的CPU负载飙升的问题
  5. jQuery Mobile滚动事件
  6. YOLOv3目标检测有了TensorFlow实现,可用自己的数据来训练
  7. 【Clickhouse】Clickhouse 数据字典
  8. docker 安装 FastDFS
  9. 美物理学家称摩尔定律将在十年内崩溃
  10. ListView的Item点击事件(消息传递)
  11. AR+LBS街景实景红包PokemonGo游戏捉妖夺宝营销解决方案定制开发暨百度高德地图Unity插件SDK
  12. OpenGL超级宝典(第7版)笔记1 清单的初始环境配置part1
  13. R语言绘制 tan 图像
  14. Kettle 添加备注
  15. Matlab帮助文档打开和命令窗口中文显示设置
  16. 【LG-P5072 [Ynoi2015]】盼君勿忘
  17. 计算机科学和热力学,相图热力学数据库及其计算软件: 过去、现在和将来
  18. python和scre_前端大牛们都学过哪些东西?
  19. mysql 上传rar文件大小_js文件上传 自定义压缩文件和文件格式及大小限制
  20. 远心镜头与普通镜头的不同处

热门文章

  1. Sharepoint 备份-还原-激活feature/工作流-安装wsp 脚本命令
  2. Spreading the Wealth(UVa 11300)
  3. 重度中暑为何致命?中国科学家找到致死机理
  4. 腾讯视频如何设置热键
  5. 夫妻合体创业,两月收入15万,他们是怎样做到的?
  6. Angular集成Bootstrap库
  7. 数说故事2022年食品饮品风味趋势报告,市场营销这样做更高效
  8. Windows 7 的摄像头在哪
  9. HP 6930p激活安装win7 OEM版
  10. Ubuntu下使用SSH 命令用于登录远程桌面