原文地址: http://oopweb.com/CPP/Documents/DebugCPP/Volume/techniques.html
这一部分介绍调试技术,包含了从阅读手册到使用工具的相关信息。
使用编译器的特性
一个好的编译器能够对你的代码做很多静态分析,该分析能够检查很多语义错误,比如类型不匹配、死代码等。对于GCC而言,有很多选项影响GCC静态分析做什么和展示什么。它主要有下面两类参数:
Warning options : GCC有很多warning 标志,很多是 “-Wphrase”这样的形式。你可以选择你感兴趣的添加到Makefile中(如果使用隐式规则,可以设置CFLAGS变量)。注意-Wall 只是包含了程序员们认为在多数情况想有用的选项。
Optimisation flags : GCC可以设置不同的优化等级。其中一些可以触发对代码的流分析。通常情况下,建议使用-O2级别。
GCC 部分 http://oopweb.com/CPP/Documents/DebugCPP/Volume/techniques.html
RTFM技术
RTFM是Read The Fine Manual的缩写。你要保证花时间找了该任务相关的文档,比如工具(不只是编译器,还有make,预处理器和链接器)、库、算法的文档。通常你不需要知道文档的所用事情,但是你必须知道这些文档是干什么的,都是什么目的。 你应当能够区分教程和参考文档:
教程只是使用示例的方法来教你怎么用。在一个教程中,传递idea 比咬文嚼字的信息重要!
参考文档假设你已经熟悉了它的topic,现在你需要找一个特定问题的确切答案。好的参考文档是非常详尽的,通过元信息(内容目录,索引,交叉引用)使你能够快速找到想要的答案。在线超文本是参考文档的一个很方便的格式。
printf() debugging
这是我们最经常碰到的形式,我们使用printf打印信息来了解控制流和变量当时的值。但是这样做有很多弊端:
1代码只是暂时加进去的,当前bug一旦解决就要删除。遇到下一bug,你又要添加相似的代码。你马上将看到有更好的方法添加调试信息。
2这样做干扰了程序的正常输出,并且降低了程序的运行速度。
3更重要的,它通常是没有帮助的。输出到stdout的信息是被缓存的,程序一旦crash,这些信息就丢失了。因此,你很可能错过了重要的信息,误导你到错误的地方寻找bug。
如果你考虑使用pringf调试,下面是几个建议:
1把错误输出到stderr,stderr是没有缓存的,这样就避免了crash时信息的丢失
2不要直接使用printf,定义一个宏使用,这样你就可以很轻松的打开或者关闭调试代码
3使用debugging level来管理调试信息
下面是一个很好的实例:
#ifndef DEBUG_H #define DEBUG_H #include <stdarg.h> #if defined(NDEBUG) && defined(__GNUC__) /* gcc's cpp has extensions; it allows for macros with a variable number of arguments. We use this extension here to preprocess pmesg away. */ #define pmesg(level, format, args...) ((void)0) #else void pmesg(int level, char *format, ...); /* print a message, if it is considered significant enough. Adapted from [K&R2], p. 174 */ #endif #endif /* DEBUG_H */ File debug.c: #include "debug.h" #include <stdio.h> extern int msglevel; /* the higher, the more messages... */ #if defined(NDEBUG) && defined(__GNUC__) /* Nothing. pmesg has been "defined away" in debug.h already. */ #else void pmesg(int level, char* format, ...) { #ifdef NDEBUG /* Empty body, so a good compiler will optimise calls to pmesg away */ #else va_list args; if (level>msglevel) return; va_start(args, format); vfprintf(stderr, format, args); va_end(args); #endif /* NDEBUG */ #endif /* NDEBUG && __GNUC__ */ }
断言 :防守式编程
代码中有很多条件判断,对于函数,有前置条件和后置条件。使用assert对这些添加判断,保证程序的正确。 asset是一个宏,你可以在编译的时候使用-DNDEBUG选项来关闭。
ANWB 调试
‘ANWB Debugging’是基于一个简单的原则: 学习东西的最好的方式是教授它们。
你找到一个,最好是不相关的,旁观者,向她解释你的代码是如何工作的。这迫使你重新思考你的假设,解释究竟发生了什么事,很多时候通过这种方式找到你的问题的原因。