【前言】本文介绍了一些实用的vs调试技巧。
目录
1.bug
Bug一词的原意是“臭虫”或“虫子”。但是“计算机错误”这个含义是近来才由于计算机技术的发展而被广大人民群众所熟知的。现在,在电脑系统或程序中,如果隐藏着的一些未被发现的缺陷或问题,也称之为“Bug”。
第一个发现Bug并将它消灭的是个程序媛——格蕾斯·赫柏,她后来成为了美国海军的一位将军。而且当年的那个Bug竟然是一个货真价实的Bug,这究竟是怎么回事呢?
历史上第一个Bug
就在历史上的今天,1947 年 9 月 9 日,赫柏及其团队发现了第一个 Bug。当天下午 3 点 45 分,赫柏在哈佛的 Mark II 电脑的日志簿上记录下了“第一个电脑故障”。问题的根源是一只飞蛾卡在了电脑的继电器触点之间,赫柏及时地把这只飞蛾粘在了 Mark II 的日志上,并用双关语写道:“第一次发现了真正的 Bug。”( “First actual case of bug being found.” )这个 Bug 其实是被其他人发现的,但是赫柏在日志上做了记录。
从此以后,人们将计算机错误称为Bug,与之相对应,人们将发现Bug并加以纠正的过程叫做“Debug”,意即“捉虫子”或“杀虫子”。
(文源知乎--北京尚学堂)
2.调试
一名优秀的程序员是一名出色的侦探。
调试就是如此。所有世界上发生的事都有迹可循,当你发现了细微的问题时,就可以顺着这些线索去找到代码出错的真相。
调试---
一个发现减少计算机程序或电子仪器设备中程序错误的一个过程。
如何去调试呢?
程序员运行程序后发现了问题,再以隔离,消除等方式去寻找错误,去找其中的错误到底在哪里,从而可以对其进行一个定位。
当知道问题在哪里后,就可以对症下药,然后进行更正,再重新交给测试人员进行debug
debug与release
debug被称为调试版本,在这个版本可以进行调试的操作。将不会做任何的优化。
release被称为发布版本,是不能被调试的,是便于用户使用的版本。
你在文件夹里也可以找到发布版本和调试版本。其中的两个exe大小是不一样的,release版本的会小一些,因为他做出了一些优化,debug可以进行调试是因为其具有调试信息。
一般我们使用的vs是在windows环境下的,是一种集成开发环境---IDE,集成了编辑器,编译器,调试器。
在调试的过程中是有很多的快捷键的。
其中的开始调试,开始执行,逐语句,逐过程,切换断点是比较重要的。
F5
启动调试,直接跳到下一个断点的位置。
打断点可以直接在代码的行数的左边上打,也可以按F9在某一行新建一个断点。再按一下就取消了。
但是两个断点不会直接进行跳跃,如果中间有scanf等需要你去操作的地方,就需要先操作,再继续。遇到循环等结构时会重复到打断点的那一行,直到出循环。但是注意断点无法进行回退到上一个断点。
在成百上千行代码中,断点的必要性就出来了。断点可以进行精确的查找纰漏。
F10 逐过程
这个过程可以是一句语句,也可以是一个函数。
F11 逐语句
逐语句与逐过程不同的是,逐语句可以进入函数的内部。
而逐过程是无法进入到这个函数的。所以只有逐语句可以让我们真正的观察到这个函数是如何进行运作的。
如果将add函数放入一个新的文件"add.h",也是会经过的。
那如何查看调试时的信息呢?
注意,必须要先开始调试才行。
没开调试
开了后
自动窗口
在里面有一个自动窗口,它可以自动的向里面放入值。同时这些值会动态的跟随你的进程发生变化。
但是如果你出了这个函数,函数里的值就不会再继续的显示了,也会增加一些你不需要观察的值。所以不常用。
监视
所以我们使用监视来进行操作。我们去添加自己想要监控的值。
同时也支持表达式的运算。
监视数组稍微麻烦点
int arr[] = { 1,2,3,4,5 };
除了看值还可以看什么呢?
查看内存
在右侧可以调整显示的列。当设置为4时,可以看到是从A4到A8的,所以地址是连续显示的。
但需要观察方便,就需要取特定的列了,比如观察int型,就选为4,就可以很明了的进行观察。
取a的地址,就可以观察a的值了。这里是以16进制显示的。而右边的那些符号是参考信息,一般不用去管他。
观察指针试试。一样的道理。
也能查看反汇编,寄存器等。
查看调用堆栈
还有调用堆栈这样的,专门针对行数较多的代码。
可以看出,main函数也是被其他的函数所调用的。
进行调试。
可以发现每执行一个test,窗口里就会多出一行。
栈具有什么特征呢?顶上先进来的先往下压,要出去的时候是从顶上出的。
随着递归的返回走,可以发现一行行的开始消失了。而且是从上往下的消失。
那么我们借助调用堆栈,就可以明白函数的调用关系了。
3.如何写出易于调试的代码
何谓好的代码?
可以正常运行。
bug较少。
可读性高,执行效率高。
注释清晰。
等等。
一些好的coding技巧
使用assert,const
对自己的编码风格有要求,例如函数名等一定要易懂
添加必要的注释
例:模拟一个strcpy库函数
#include<stdio.h>
void my_strcpy(char* dest, char* str)
{
while (*str != '\0')
{
*dest++ = *str++;
}
*dest = *str;
}
int main()
{
char ch1[] = "qqqqqq";
char ch2[] = "0";
my_strcpy(ch1,ch2);
printf("%s", ch1);
}
可见其严谨性并不好。因为当传进来空指针的话就会有报错。
可以添加断言assert(头文件assert.h)
assert中可以放一个表达式,若为真则继续,假则报错。
#include<stdio.h>
#include<assert.h>
void my_strcpy(char* dest, char* str)
{
assert(dest != NULL && str != NULL);
while (*str != '\0')
{
*dest++ = *str++;
}
*dest = *str;
}
int main()
{
char ch1[] = "qqqqqq";
char ch2[] = "0";
char* p = NULL;
my_strcpy(ch1,p);
printf("%s", ch1);
}
传入的str为空,则报错。
还有没有其他的问题呢?例如我们函数中传入的那个str是不希望被修改的,所以可以将其改成
const char* str
这样就可以避免在函数里的书写错误,将str和dest的位置颠倒造成改变str。
利用const来对指针变量进行修饰。
const放在*的左边,限制的是指针指向的内容,不可以进行解引用来修改,但可以修改p
const int* p=#
*p=20; //不可行
p=n; //可行
const放在*的右边,就对p有了限制。
int* const p=#
*p=20; //可行
p=n; //不可行
除此之外,还需要在代码的关键地方去添加注释,增强代码的可维护性。
为函数取名时尽量让人一看就知道这个函数是为了实现什么功能。
还有就是面对常见的报错信息可以快速的反应可能是哪些地方出现了问题。