目录
一、什么是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 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
接下来调试下方代码
#include<stdio.h>
int main()
{char* p = "hello word!";printf("%s\n",p);return 0;
}
在debug版本下 (执行程序)文件名.exe 是几十KB
而在release版本下 是 几 KB(原因是代码大小和运行速度上都是最优的)
再看下方代码
#include<stdio.h>
int 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! ;不考虑溢出
#include<stdio.h>
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;//添加的代码for (j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;
}
【例 2 】死循环的原因
#include<stdio.h>
int 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<stdio.h>
#include<assert.h>
char* 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<stdio.h>
#include<assert.h>
char* 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 =#
p = &n; //ok
*p = 20; //error
const 在 * 右边
int n = 1000;
int num = 0;
int * const p = # //限制了指针变量本身
p = &n; //error
*p = 20;//ok
【小总结】
const 修饰指针变量的时候:
- const放在 * 左边,修饰的是指针指向的内容,保证指针指向的内容不被修改。但是指针变量可以修改
- const 放在* 右边,修饰的是指针变量本身,保证指针变量本身不被修改。但是可以修改指针指向的内容
练习:模拟实现strlen
//模拟实现strlen
#include<stdio.h>
#include<assert.h>
int 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;
}
八、编程常见的错误
-
编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定
- 链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不
存在或者拼写错误
- 运行时错误
借助调试,逐步定位问题。