- 什么是bug?
- 什么是调试?调试能力有多重要?
- debug和release的简单介绍
- windows环境下调试介绍
- 一些调试的实例
- 如何写出易于调试的代码?
- C编程中常见的错误
- 模拟实现strcpy,strlen库函数
测试环境:vs2022
一、什么是bug?
第一次被发现的导致计算机错误的飞蛾,也是第一个计算机程序错误。
二、什么是调试?调试能力有多重要?
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有 愧,就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。顺 着这条途径顺流而下就是犯罪,逆流而上,就是真相。每个优秀的程序员都是一名出色的侦探,当遇到bug时,每一次的调试都是尝试破案的过程.
调试(Debugging/Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。
调试的基本步骤
- 发现程序错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决办法
- 对程序错误予以改正,重新测试
三、debug和release的简单介绍
Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
代码演示:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = i + 1;
}
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
所以我们说调试就是在Debug版本的环境中,找代码中潜伏的问题的一个过程。
四、windows环境下调试介绍
-
调试环境的准备:在环境中选择 debug 选项,才能使代码正常调试。
2.常用调试快捷键(熟练)
F5:启动调试,经常用来直接调到下一个断点处。
F9:创建断点和取消断点 断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得 程序在想要的位置随意停止执行,继而一步步执行下去.先按F9,再按F5。
F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11:逐语句,每次都执行一条语句,这个快捷键可以使我们的执行逻辑进入函数内部(最常用)。
Ctrl + F5:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
调试启动之后,才能看到窗口的相关信息.
3. 调试时查看程序当前信息
代码演示:
①查看临时变量的值:在调试开始之后,用于观察变量的值。
②查看内存信息:在调试开始之后,用于观察内存信息。
一个十六进制数字是四个二进制位,两个十六进制是八个二进制位(一个字节)
③查看调用堆栈:返回的是函数的调用逻辑
代码演示:
void test2()
{
printf("hehe\n");
}
void test1()
{
test2();
}
void test()
{
test1();
}
int main()
{
test();
return 0;
}
一些调试的实例
实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。
问题代码:
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
int sum = 0;
int j = 0;
for (j = 1; j <= n; j++)
{
for (i = 1; i <= j; i++)
{
ret = ret * i;
}
sum = sum + ret;
}
printf("%d\n", sum);
}
用调试的方法进行错误分析:
正确代码:
运行错误:调试解决的就是运行错误
解决问题:
应该是什么结果 - 预期 调试的时候发现不符合预期就找到了问题.
实例2:以下代码的运行结果是什么?
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("hehe\n");
}
return 0;
}
用调试的方法进行分析:
简要分析:
- i和arr是局部变量,局部变量是放在栈区上的.
- 栈区内存的使用习惯是:先使用高地址空间,再使用低地址空间.
- 数组随着下标的增长,地址是由低到高变化的.
- 数组用下标访问的时候,只要适当地往后越界,就有可能覆盖到i
- i一旦被覆盖的话,就有可能会导致死循环
- 不同编译环境下的预留空间:vc6.0 0个整形;gcc 1个整形 vs 2个整形
改进代码:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
//0 - 9
for (i = 0; i <= 12; i++)//一直在死循环没有机会报错
{
// i <= 11越界了但没有造成死循环,会报错
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
预防发生错误:通过编码技巧,减少错误.
六、如何写出易于调试的代码?
优秀的代码:
- 代码运行正常
- bug很少
- 效率高
- 可读性高
- 可维护高
- 注释清晰
- 文档齐全
常见的编码技巧:
- 使用assert
- 尽量使用const
- 养成良好的编码风格
- 添加必要的注释
- 避免编码的陷阱
七、C编程中常见的错误
常见的错误分类:
编译型错误:直接看错误提示信息(双击),解决问题.凭借编码经验就可以搞定,相对简单.
链接型错误:看错误提示信息,主要在代码中找到错误信息中的标识符,定位问题.一般是标识符名不存在或者拼写错误
运行时错误:借助调试逐步定位问题,相对困难.
温馨提示:做一个有心人,积累排错经验
八、模拟实现strcpy,strlen库函数
int main()
{
char arr1[20] = "xxxxxxxxxxxx";
char arr2[] = "hello";
//strcpy(arr1,arr2);
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
方法一:
#include <string.h>
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;
}
方法一中的代码优化:
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest++ = *src++;
}
*dest = *src;
}
注: *解引用操作符的优先级比++操作符的优先级高,先进行赋值运算,再各自自增,无关自增顺序
方法二:
void my_strcpy(char* dest, char* src)
{
while (*dest++ = *src++)
{
;
}
}
代码简要分析:
- *dest++ = *src++比较字符的ASCII码值,0为假,非0为真,'\0'的ASCII码值为0.
- 0为假,不进入while循环.非0为真,进入循环.
- 空语句的作用:既拷贝了'\0',又使得循环停止.