目录

一、什么是bug?

二、调试

1.一般调试的步骤

2.Debug 和 Release

三、调试环境准备

四、调试时要查看的信息

1.查看临时变量的值

2.查看内存信息

3.查看调用堆栈

4.查看反汇编信息

5.查看寄存器

五、练习

六、常见的coding技巧

七、const的作用

八、编程常见的错误


一、什么是bug?

我们平时会口头说 bug ,报错,waring(报警)等,bug 英文的意思是虫子,然而在计算机发展史上的第一只 Bug ,真的是因为一只飞蛾意外走入一电脑而引致故障,因此Bug从原意为臭虫引申为程序错误。

当我们

这个时候就需要我们的调试 来开启新大陆

关于程序错误的参考资料

二、调试

平时敲代码,总会遇到与一些问题导致程序执行不过去,你可能在那一直盯着刚写完的代码看(心里想这到底哪里出错了,但是就是没有找打错误的原因),这时就需要我们平时了解到的调试来解决问题(起先使用可能不熟练,慢慢来)

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

1.一般调试的步骤

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

2.Debug 和 Release

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

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

接下来调试下方代码

#includeint main() {char* p = "hello word!";printf("%s\n",p);return 0;}

在debug版本下 (执行程序)文件名.exe 是几十KB

而在release版本下 是 几 KB(原因是代码大小和运行速度上都是最优的)

再看下方代码

#includeint main() {int i = 0;int arr[10] = { 0 };for (i = 0; i <= 12;i++){arr[i] = 0;printf("haha\n");}return 0;}

在 vs2022 x86 debug 的环境下

该程序的【执行结果】 无限循环打印 haha

而在release版本下

没有死循环 打印了13行的haha

二者区别是因为:变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果

三、调试环境准备

如果要对代码进行调试首先要准备好调试的环境

就是要在debug版本下,才能使代码正常调试

(点击开始调试)或者按F5

在这里介绍一些调试的快捷键

  • F5 启动调试,经常用来直接跳到下一个断点处
  • F9创建断点和取消断点。断点的重要作用,可以在程序的任意位置设置断点。
    这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去
  • F11 逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最长用的)
  • F10逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句
  • Ctrl + F5开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用

其他快捷键

四、调试时要查看的信息

1.查看临时变量的值

在按调试后,观察变量的值

例如 输入 i

一直按F11当 i 的值变为 11时 i值的变化(0-11)

2.查看内存信息

在内存窗口 输入 &i(找到i 的内存地址)

3.查看调用堆栈

反映的是调用逻辑

4.查看反汇编信息

5.查看寄存器

五、练习

【例 1】

//实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出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;}

输入 1,输入2 和我们预想的结果一样,但当我们输入 3 的时候结果应该是 9 实际输出结果为:

打印的结果出错了

接着进行调试,当调试到 i= 2是 正常的

调试到 j = 3 是 ret 应该是 6 ,但是发现 ret由4 变到 12

经果分析我们发现 原来是ret 每次进入内层的for循环 ret 的值接着上次的执行结果继续算

这时 我们在内层for循环上方加上 ret =1;

//实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出#includeint 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;//添加的代码for (j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;}

【例 2 】死循环的原因

#includeint main() {int i = 0;int arr[10] = { 0 };for (i = 0; i <= 12;i++){arr[i] = 0;printf("haha\n");}return 0;}

调试后发现

六、常见的coding技巧

  • 使用assert(断言,是一个宏,在release版本中会自动优化掉)
  • 尽量使用const(下面会讲到用法)
  • 养成良好的编码风格
  • 添加必要的注释
  • 避免编码的陷阱

【例】模拟实现库函数strcpy、

库函数strcpy

//模拟实现strcpy#include#includechar* my_strcpy(char *des,const char *src){assert(des != NULL);assert(src != NULL);//避免字符串为空char* temp = des;while (*des){*des = *src;des++;src++;}return (temp);}int main(){char* str = "ab";char arr[20] = "xxxxxxxxxx";printf("%s\n",my_strcpy(arr,str));return 0;}

优化

#include#includechar* my_strcpy(char* des,const char *src) {assert(des != NULL);assert(src != NULL);char* temp = des;//用于返回首元素地址while (*temp++ = *src++);return des;}int main() {char *arr1 = "abcdef";char* arr2[20] = {0};printf("%s\n",my_strcpy(arr2,arr1));return 0;}

七、const的作用

const 在 * 左边

int num =0;int n = 0;const int *p =&num; p = &n;//ok*p = 20; //error

const 在 * 右边

int n = 1000;int num = 0;int * const p = &num; //限制了指针变量本身p = &n; //error*p = 20;//ok 

【小总结】

const 修饰指针变量的时候:

  1. const放在 * 左边,修饰的是指针指向的内容,保证指针指向的内容不被修改。但是指针变量可以修改
  2. const 放在* 右边,修饰的是指针变量本身,保证指针变量本身不被修改。但是可以修改指针指向的内容

练习:模拟实现strlen

//模拟实现strlen#include#includeint my_strlen(const char* str) {assert(str != NULL);int count = 0;while (*str) {count++;str++;}return count;}int main() {char* str = "abcdefg";printf("%d\n",my_strlen(str));return 0;}

八、编程常见的错误

  • 编译型错误

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定

  • 链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不
存在或者拼写错误

  • 运行时错误

借助调试,逐步定位问题。