此文是作者最初学习C语言时的笔记博文,如有错误请积极指正。谢谢!!!
目录
1,什么时bug
bug是计算机领域专业术语,原意是臭虫,现在用来指代计算机上存在的漏洞。原因是系统安全策略上存在的缺陷,有攻击者能够在未授权的情况下访问的危害。
2. 调试是什么?有多重要?
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧, 就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。 顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
一名优秀的程序员是一名出色的侦探。
每一次调试都是尝试破案的过程
2.1 调试是什么?
调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序 错误的一个过程。
2.2 调试的基本步骤
1,发现程序错误的存在
2,以隔离、消除等方式对错误进行定位
3,确定错误产生的原因
4,提出纠正错误的解决办法
5,对程序错误予以改正,重新测试
2.3 Debug和Release的介绍。
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
所占用的内存更大,因为包含调试信息。
Release 称为发布版本,它往往是进行了各种优化,不能调试,使得程序在代码大小和运行速度上都是最优 的,以便用户很好地使用。
占用的内存就相对小一些了 ,不包调试信息。
3. Windows环境调试介绍
3,1准备调试环境
(修改下文件环境改为debug(调试)版本),
release版本为发布版本是不能进行调试的,以便用户更好的使用。
3.2 学会快捷键
vs中有很多的快捷键提供给我们使用
f5:启动调试,经常用来直接跳到下一个断点处。
f9:创建断点和取消断点
断点的重要作用:可以在程序的任意位置设置断点。 这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
所以f5一般不能单独使用,要和f9配合使用,
条件断点
右键点击红点可以设置条件断点
f10:逐过程,一个过程可以是一次函数调用,或者是一条语句。
f11:逐语句(可以进入函数内部)
CTRL + F5:开始执行不调试,不受断点的影响。
注意有时候我们需要一些健来配合使用
win-->fn+F5 ,fn+F10-----
mac--->command+F5----
vs中还有很多的快捷键,建议大家去实际操作一下。
3.3 调试的时候查看程序当前信息
当代码调试起来的时候,我们在调试---窗口-里面可以看到各种各样的一些表示程序信息的窗口供我们使用,例如常用的 监视, 自动监视, 局部变量, 内存, 调用堆栈, 反汇编, 寄存器------
4,多多动手,尝试调试,才有进步
一定要熟练掌握调试技巧。
初学者可能80%的时间在写代码,20%的时间在调试。
但是一个程序员可能20%的时间在写 程序,但是80%的时间在调试。
我们所讲的都是一些简单的调试。
以后可能会出现很复杂调试场景:多线程程序的调试等。
多多使用快捷键,提升效率。
5,实例
5.1实例一:
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;
}
这时候我们如果3,期待输出9,但实际输出的是15。原因:ret的定义在循环的外面,ret存的是每一次n的阶乘,但是定义在外面,他就会记住上一次n-1的阶乘的结果,这就会出错,应该在循环内部重新赋值或者定义在循环内部。
5.2 实例二:
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
第二个实例。可能会导致死循环(依赖环境有时候不会死循环),死循环的原因是:
这个程序是先创建 i 再创建arr ,栈区的使用习惯是先使用高地址的空间再使用低地址的空
间,数组地址的习惯是随着下标的增长地址逐渐增大,刚好的 i 变量的地址,比arr最后一个元素的地址刚好大两个整型的地址空间,所以arr[12]在越界访问的时候刚刚好访问到 i 的地址修改了 i 的数值,把 i 修改为0;循环刚好有往复了,这就会造成死循环。
当前代码在不同的环境下效果是不一样的,
1,在vc6.0中 i 和arr最后一个元素地址没有空间
2,在gcc中 i 和arr最后一个元素地址空出了一个整型的空间。
3,把arr和 i 的定义顺序调换一下就不会死循环了,这时会越界访问。
4,在release版本下他也不会死循环,release版本会优化代码,vs在release版本下内存布局会发生改变 , i 的地址比arr的地址小,也就不会出现死循环了。被优化了。
5.在x64 i 和arr的地址也没谱,实际运行会报错越界访问(vs),在x86下就会死循环,说明这个代码是很依赖环境的。
推荐一个条调试的方法:就是模拟给别人讲自己的代码的这个过程来看自己写的代码,在讲的过程中可能就会找到自己的错误,和自己的代码是否合理。
6. 如何写出好(易于调试)的代码。
6.1 优秀的代码:
1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全
常见的coding技巧:
1. 使用assert
2. 尽量使用const
3. 养成良好的编码风格
4. 添加必要的注释
5. 避免编码的陷阱。
6.2 示范:
模拟实现strcpy
三个版本!!
//减配版
void my_strcpy(char* arr2, char* arr1)
{
while (*(arr1 -1) != '\0')
{
*arr2 = *arr1;
arr1++;
arr2++;
}
}
//中配版
#include<assert.h>
void my_strcpy(char* arr2,const char* arr1)//const修饰的常量就不可被更改
{
assert(arr1 && arr2);
while (*arr2 = *arr1)
{
arr1++;
arr2++;
}
}
//高配版
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* arr2,const char* arr1)
{
char* mid = arr2;
assert(arr1 && arr2);//断言一下arr1 和arr2不是空指针
while(*arr2 = *arr1)
{
arr1++;
arr2++;
}
return mid;
}
//顶配版
char* my_strcpy(char* arr2,const char* arr1)
{
char* mid = arr2;
assert(arr1 && arr2);//断言一下arr1 和arr2不是空指针
while(*arr2++ = *arr1++)
{
;
}
return mid;
}
7,编程常见的错误
7.1 编译型错误
编译型错误就是语法错误,比如少个括号,或者少个是其他的什么符号。这个错误一般编译器就会给你找出来,直接看错误信息的提示,含容易就可以解决了。他们都是固定的常见的错误。双击错误信息就会跳到错误附近。再修改即可。
7.2 链接型错误
在链接期间发生的错误,在我们运行程序的时候源文件test.c 会生成一个可执行程序test.exe。这个过程有俩个步骤第一个叫编译 ,第二个叫链接。例如:
1,在未包含头文件的时候,编译器会报错说无法解析的外部符号。
2,在引用自定义函数时,尚未编写就开始运行,这个时候也会报错,无法解析的外部符号x'x'x。该函数未定义。
3,或者自定义函数名写错了,也一样的。这个时候我们可以搜索一下,ctrl+f 就会出现搜索框。搜索想要找的函数名。
这种错误一般解决要看错误信息提示,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符不存在或者拼写错误。如果是库函数,也可能是未引头文件。
7.3 运行时错误
运行时错误,也就是结果不是我们想要的, 这个时候就是我们说的程序有bug了,这个错误可能就是逻辑错误或者是其他错误了。
这种错误 想要解决一般要通过调试来观察代码的错误了,这个错误才是最致命的。
8.拓展
1,在内存中栈区的使用习惯是先使用高地址的空间,再使用低地址的空间。
数组随着下标的增长地址是由低到高变化的
strcpy:库函数,字符串拷贝。<string.h>,包含'\0'。
const:就是修饰指针的。
const char* str = &a;
char const *str = &a;
const 这样写的时候,也就代表*str指向的空间不可被更改((就是a的值不能更改),但是str可以被更改可以改为&b。
char * const str = &a;
const这样写的时候,也就是代表*str可以被更改(就是a的值可以被改),但是str不可被更改也就是&a不能改。
assert:库函数<assert.h> 意思是断言一些条件。
const修饰的常量
const int num = 10; //这里不能用num = 20;直接去更改他 //但是可以这样改 int * pa =&num; *num = 20; //这样a的值就被改掉了 感觉这个操作不合理的 这个仿佛是C语言的一个bug