最好用的调试方法肯定是printf了:-),比如说,我们可以这么打印z的值:

z = x + y;
printf ("z = %d\n", z);

然而,我们常常只对printf的部分感兴趣,因此,我们可以使用#ifdef宏

z = x + y;
# ifdef DEBUG_MODULE_A
printf ("z = %d\n", z);
# endif

但这又引出了一个问题,每次都要写#ifdef未免有点cumbersome了.为了使代码轻松一点,我们可以这么写:

z = x + y;
debug_module_a ("z = %d\n", z);

或者:

debug ( module_a , "z = %d\n", z);

写的规范点,代码就是这个样子了,

z = x + y;
DBG(("z = %d\n", z));

DBG的定义为:

# ifdef PRGDEBUG
# define DBG (x) printf x
# else
# define DBG (x) /* nothing */
# endif

其实,上面的做法都是很常见的.但在添加模块后,调试宏应该怎么写就确实能够区分出做好的好坏了.

首先,为了简洁,我们当然还是希望打印状态时可以这么写的,

DBG (( MOD_PARSER , "z = %d\n", z ));

这里的模块编号MOD_PARSER也可以是MOD_SOLVER或者是M0D_PRINTER,因为多了这么一个参数,printf的方法便失效了,所以,我们还需要额外定义一个状态输出函数:

void dbg_printer ( int module , const char *fmt , ...) {}

宏的定义为:

# ifdef PRGDEBUG
# define DBG (x) dbg_printer x
# else
# define DBG (x) /* nothing */
# endif

使用如下:

z = x + y;
DBG (( MOD_PARSER , "z = %d\n", z ));

我们知道,既然需要动态地添加模块,那么程序往往是会提供一个配置文件的,比如说一个名为"debug_modules.conf"的文件,内容如下:

ADD_MOD (0, PARSER )
ADD_MOD (1, SOLVER )
ADD_MOD (2, PRINTER )
//后面可能是一些不必使用的模块配置
UNNEEDED_MOD(3, PREPROCESSOR)

它表示程序将加载的模块.这时又引出了一个问题:在调试相关的代码中,我们需要提供一个函数读配置文件和所需模块所在的行号,这时代码也许就变成了:

# define DBG (x) dbg_set_pos ( __FILE__ , __LINE__ ), \
dbg_printer x
static const char * file_name ;
static int line_num ;
void dbg_printer ( int module , const char *fmt , ...) { ... }
void dbg_set_pos ( const char * file_name , int line_num ) { ... }

这时代码就变得相当难看了,怎么才能优雅起来呢?一个不错的方法是,使用宏连接:定义一个头文件"debug.h",内容为:

...
# define ADD_MOD (num , id) MOD_ ## id = 1 << num ,
enum _debug_modules_t {
# include "debug_modules.conf"
};
# undef ADD_MOD
...

在调试代码中,直接包含该头文件即可,头文件内容将被预处理器自动转换为:

enum _debug_modules_t {
MOD_PARSER = 1 << 0,
MOD_SOLVER = 1 << 1,
MOD_PRINTER = 1 << 2,
};