debug可以帮助熟悉系统,可是时间长了会很疲卷,特别是机械的调试,如果还要面对杂乱的代码,更是雪上加霜。所以要学着从debug中钻探快乐,在系统的调试过程中发挥想象,尝试不同的debug方法。
最近看了《软件调试实战》,结合自己的经历,总结了一下:
1. 与测试用例相关
a. 如果不能达到“测试先行”,至少应该在写完代码后有相对完整的测试用例。对于正确性的保证和以后重构代码都是有好处的。
b. 每次添加新功能或修复了一个bug时,都应该增加测试用例!A历经千辛万苦终于fix 了一个bug,很久很久以后,B觉得这段代码需要改改,于是改了改,后来的结果还是改了,而且顺利提交到了库里(因为A当时遇到的bug 并没有出现!)
c. 回归测试
修改代码后进行回归测试。每次提交一个版本后自动进行回归测试,保证库里的代码的正确性。
d. 简化测试用例
好处:可以排除不起作用的因素;减少测试用例的运行时间;最重要的是,使用测试用例更容易调试(谁愿意处理那些填充了数百或数千项的数据容器呢?)
方法如: 如果测试例子比较好改,可以将其改小;将输入集改小
e. 完成代码,清理后重新运行所有测试用例。
2. 关于程序的编译
a. 重视编译期间的warning,最好把warning数减为0. 不要忽略编译器警告,即使它们可能是无害的。
eg:
int add(int a,int b){
return a +b ;
}
结果头文件里声明成了 extern int add(long a,int b)
会调试死人啊,调程序的时候一看程序定义是对的啊,怎么传的参数一下就变了;
b. 如果出现莫名其妙的错误
如果是用Makefile组织工程时,考虑make clean,有可能修改数据结构或头文件后改变了一些东西,但是由于一些未知原因该文件并未重新编译。如果函数是C函数,有可能调用者和被 调用者的参数的成员和类型不同。如果一个类方法,则访问任何类成员 都将发生错误,因为这两个类的内存而已几乎是完全不同的。这可能导致Segmentation falut,或是很久之后才能检测到的内存破坏。
3. 关于链接
a. 链接器的基本工作原理
编译器或汇编程序将源代码转换为机器代码,并输出对象谁的。对象文件中包含符号(函数或变量),这些符号有的在本模块定义的,有的在其他模块定义的,链接器就在链接对象文件时把这些未定义的符号与定义它的模块对应起来。
b. 链接顺序
有库和归档文件时 链接算法是不一样的。
链接器参数顺序很重要,对于编译单元(如对象文件和库)和搜索路径来说都是如此。
c. C++中使用C代码时,用extern c{} 把C代码包装一下。
关于 c++符号和名称改编:C++允许重载函数,为了生成C++代码元素的唯一符号,编译器使一种称为名称改编(name mangling)的技术,它将对象的准确规格说明(如会员名空间和函数参数的个数及类型)编码到符号中。(可以用c++filt解析出来~ eg: c++filt _Z9factoriali的结果为factorial(int))
d. 环境变量
LD_LIBRARY_PATH会影响动态加载的库,用LDD可以看到程序依赖哪个动态库
4. 自动化测试
让一切自动化起来。如果重复的做一件事,就很有必要考虑自动化了。
5. 关于那些怪异的错误
在一些显而易见有内存问题的情况下,如:间歇故障和无法解释的随机行为,这时考虑使用内存调试器了!
如valgrind,很好用,也很简单。
valgrind –tool=massif your_program 进行内存剖析(检测内存分配情况,优化内存使用)
valgrind –tool=memcheck your_program 进行内存检查(检测无效的写访问,检测对未初始化的内存的读取操作,检测内存泄露等)
valgrind –tool=helgrind your_program 查找竞争条件,可以用来辅助调试多线程程序
valgrid –-db-attac=yes的功能很好用,可以将内存高度器和源代码测试器(如gdb)结合起来,这样就可以即时查看当时的变量的值,很好用!
6. 静态检查器
作为常规软件构建过程中的一部分运行,用于查找一些可通过静态源代码分析发现的特定bug。
7. 关于运行时剖析工具
不要编写自己的运行时剖析时工具:自己霞友云朋一的剖析 工具通常使用系统调用time()或ctime()来测量时间。这些系统调用的问题是开销很高,而且准确度低。另处在剖析期间要收集大量数据,可能会影响程序本身的行为。
8. 环境变量
如程序的行为可能 依赖于当前工作目录。在linux上,目录被注册到环境变量CWD上。这个bug碰到过,还导致了死锁。
9. 读取恰当的错误消息
某个地方出错时,满屏都是错误消息时,应该重点关注哪些消息?
Answer: 首先出现的那些消息!因为后面的消息有可能是前面导致的。这和编译出错时的情景一致:编译错误有很多,我们肯定会直觉地去寻找第一个出错的 地方,谁知道是不是少了个括号导致后面一连串的错误。
10. bug不会自动消失
如果某个版本有bug,update后,bug消失了,“真好!”,一定要弄清楚bug出现的原因是什么。以前遇到过一个bug,增加一条printf语句后,bug消失了!最后发现问题是数组越界了,而修改源代码会导致代码段,数据段的布局等改变,所以会导致偶尔对。(这种情况可以求助于内存调试工具或者静态检查的工具)
11. 学习使用gcc, gdb,strace 等工具。(熟悉以后可以再挖掘挖掘,可能有惊喜)
12. cvs/svn commit之前一定要diff一下,看做了哪些修改,以避免不小心删掉一些东西后,然后”被提交”了。
最后,最强大的工具不在计算机中,而是调试者的判断力和分析技巧。
参考资料:
1. 《软件调试实战》:http://book.douban.com/subject/4231293/
posted on 2011-05-17 20:26
hex108 阅读(439)
评论(0) 编辑 收藏 引用 所属分类:
Program