在調試程序時,輸出調試信息是一種普遍、有效的方法。輸出調試信息一般有以下五種方法:
方法一:直接使用屏幕打印函數printf。
該方法直接在需要輸出調試信息的位置使用函數printf輸出相應的調試信息,以及某些關鍵變量的值。我們通過以下求階層的函數fact來看看該方法的調試程序過程。
#include <stdio.h>
int fact(int n)
{
int i,f=1;
for( i=1; i<=n; i++)
{
f += i;
}
return f;
}
int main()
{
printf( "4!=%d/n", fact(4) );
return 0;
}
程序1: 有bug的求階層函數
程序1編譯運行的結果如下:
4!=11
結果錯誤。爲了找到結果錯誤的原因,我們在語句"f += i;"之後插入函數printf輸出調試信息,如程序2。
#include <stdio.h>
int fact(int n)
{
int i,f=1;
for( i=1; i<=n; i++)
{
f += i;
printf("i=%d ; f=%d/n", i, f);
}
return f;
}
int main()
{
printf( "4!=%d/n", fact(4) );
return 0;
}
程序2: 加入函數printf輸出調試信息的求階層函數
再編譯運行該程序,屏幕輸出如下:
i=1 ; f=2 i=2 ; f=4 i=3 ; f=7 i=4 ; f=11 4!=11原來語句"f += i"錯了,應該爲"f *=i"。修改過來(見程序3),再編譯運行,結果如下:
i=1 ; f=1 i=2 ; f=2 i=3 ; f=6 i=4 ; f=24 4!=24 #include <stdio.h>
int fact(int n)
{
int i,f=1;
for( i=1; i<=n; i++)
{
f *= i;
printf("i=%d ; f=%d/n", i, f);
}
return f;
}
int main()
{
printf( "4!=%d/n", fact(4) );
return 0;
}
程序3: 修改正確的求階層函數
調試完成,bug找到,並修改正確。然後將加入的調試的函數printf 刪除或註釋掉。
該方法的缺點是(1)在正式發佈的程序中需要去除或註釋掉這些調試語句;(2)若程序又出現bug,則又需要重新插入函數printf輸出調試信息,造成工作的重複。
方法二:自定義調試函數debug。
爲了避免方法一的缺點,可以利用條件編譯技術,如程序4自定義調試函數debug。當程序正式發佈的編譯時取消宏定義__DEBUG__,在正式發佈的程序中就不會輸出調試信息。若又出現bug,只要重新在編譯程序時定義宏__DEBUG__即可恢復原來的調試信息輸出。可以在編寫程序時就有目的事先插入些調試語句,這將有益於調試程序。另外,可以根據需要編寫函數debug,將調試信息輸出到除屏幕以外的其它地方,如文件或syslog服務器等。
#include <stdio.h>
#ifdef __DEBUG__
#include <stdarg.h>
void debug(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
#else
void debug(const char *fmt, ...)
{
}
#endif
int fact(int n)
{
int i, f = 1;
for( i=1; i<=n; i++)
{
f *= i;
debug("i=%d ; f=%d/n", i, f);
}
return f;
}
int main()
{
printf( "4!=%d/n", fact(4) );
return 0;
}
程序4: 自定義調試函數debug
該方法的缺點是(1)調試信息要麼全部輸出,要麼全不輸出;(2)要重新輸出調試信息時需要重新編譯程序。
方法三:含調試等級的自定義調試函數debug。
可以繼續改進方法,避免方法二中的缺點。我們可以根據調試信息的細節程度,將調試信息分成不同的等級。調試信息的等級必須大於0,若調試信息細節程度越高,則等級越高。在輸出調試信息時,若調試等級高於調試信息等級才輸出調試信息,否則忽略該調試信息,如程序5。當調試等級爲0時,則不輸出任何調試信息。
#include <stdio.h>
#include <stdlib.h> /* atoi() */
#include <stdarg.h>
int debug_level;
void debug(int level, const char *fmt, ...)
{
if( level <= debug_level )
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
}
int fact(int n)
{
int i, f = 1;
for( i=1; i<=n; i++)
{
f *= i;
debug(250, "i=%d ; f=%d/n", i, f);
}
return f;
}
int main(int argc, char *argv[])
{
if ( argc < 2 )
{
debug_level = 0;
}
else
{
debug_level = atoi(argv[1]);
}
printf( "4!=%d/n", fact(4) );
return 0;
}
程序5: 含調試等級的自定義調試函數debug
用命令"gcc -Wall -o fact fact.c"編譯程序5,得到可執行文件 fact。若需要輸出調試信息,只需要指定調試等級不低於250即可,如運行命令"./fact 250",否則將不會輸出調試信息。
這樣,在正式發佈版中包含調試信息也無傷大雅了,因爲只需將調試等級配置爲0,將不會出現任何調試信息。
該方法的缺點是效率不太高,因爲不管調試信息是否需要輸出,都會進行一次函數調用。若不需要輸出調試信息,這次函數調用就多餘了。
方法四:調試等級的判斷放在自定義調試函數debug之外。
爲了減少不必要的函數調用,可以用宏定義將調試等級的判斷放在函數debug之外,如程序6。
#include <stdio.h>
#include <stdlib.h> /* atoi() */
#include <stdarg.h>
int debug_level;
#define debug(level, fmt, arg...) /
if( level <= debug_level ) __debug(fmt, ##arg)
void __debug(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
int fact(int n)
{
int i, f = 1;
for( i=1; i<=n; i++)
{
f *= i;
debug(250, "i=%d ; f=%d/n", i, f);
}
return f;
}
int main(int argc, char *argv[])
{
if ( argc < 2 )
{
debug_level = 0;
}
else
{
debug_level = atoi(argv[1]);
}
printf( "4!=%d/n", fact(4) );
return 0;
}
程序6: 調試等級的判斷放在自定義調試函數debug之外
這種方法對於不需要輸出的高等級的調試信息操作來說,僅僅多了個兩個整數之間的大小判斷。在正式的程序運行時,效率是有所提高的。
但這種調試信息輸出的方法依然不夠完美。對於一個大項目,一般分爲若干個模塊,bug將會定位到某個或某幾個模塊。若整個項目的調試信息都輸出,信息量將會非常大,也容易干擾調試人員的思維。這時,我們需要的是隻輸出我們關心的那些模塊的調試信息,但該方法並不能達到我們的要求。它只能根據調試等級輸出信息,對於同一調試等級的信息要麼全輸出,要麼全不輸出。
方法五:根據不同的功能模塊分別定義不同的調試等級。
在squid[1]中,定義了以下的功能模塊調試等級變量和調試函數:
int debugLevels[MAX_DEBUG_SECTIONS];
#define debug(SECTION, LEVEL) /
((_db_level = (LEVEL)) > debugLevels[SECTION]) ? (void) 0 : _db_print
然後在程序中如下使用它:
debug(17, 3) ("fwdStateFree: %p/n", fwdState);
上述調試函數很靈活,可以在不同的模塊中定義有不同的調試等級,當需要調試某功能時,只需將該模塊的調試等級定義爲相應的等級,就可輸出需要的調試信息。
根據方法五的思想,本人編寫了my_debug.h(見程序7)和my_debug.c 文件(見程序8)。該文件可以應用於C語言程序中,支持根據不同的功能模塊分別定義不同的調試等級。
#ifndef MY_DEBUG_H
#define MY_DEBUG_H
#include <stdio.h>
// 模塊功能號
enum {
MY_SECTION_FACT = 0,
MY_SECTION_nnn1,
MY_SECTION_nnn2,
MY_SECTION_nnnn,
MY_SECTION_END,
};
// 非my_debug.c文件的外部變量聲明
#ifndef MY_DEBUG_C
extern int __my_allow_debug_levels[MY_SECTION_END];
#endif
// (內部使用) 判斷"SECTION"模塊功能號是否允許"DEBUG_LEVEL"等級的調試信息輸出
#define __my_unallow_debug(SECTION, DEBUG_LEVEL) /
( DEBUG_LEVEL > __my_allow_debug_levels[SECTION] )
// (內部使用) 調試信息輸出函數
#define __my_debug(FORMAT, ARG...) /
printf("%s:%d %s: " FORMAT, __FILE__, __LINE__, __FUNCTION__, ##ARG)
// 初始化"SECTION"模塊功能號的調試等級
#define my_init_debug_levels(SECTION, ALLOW_DEBUG_LEVEL) /
( __my_allow_debug_levels[SECTION] = ALLOW_DEBUG_LEVEL )
// 調試信息輸出函數,該信息爲"SECTION"模塊功能號"DEBUG_LEVEL"等級的調試信息
#define my_debug(SECTION, DEBUG_LEVEL) /
( __my_unallow_debug(SECTION, DEBUG_LEVEL) ) ? (void) 0 : __my_debug
#endif //MY_DEBUG_H
程序7: my_debug.h
#define MY_DEBUG_C
#include "my_debug.h"
int __my_allow_debug_levels[MY_SECTION_END];
程序8: my_debug.c
要使用上述文件,先得根據功能模塊的數目擴展my_debug.h中的“模塊功能號”枚舉類型,然後在程序相應位置中調用宏定義my_init_debug_levels 初始化相應模塊的調試等級,在所有需要輸出調試信息的位置如下編寫即可。
my_debug(MY_SECTION_FACT, 250)("i=%d ; f=%d/n", i, f);
下面我們來看看如何在fact.c中使用它們(見程序9)。
#include <stdio.h>
#include <stdlib.h>
#include "my_debug.h"
int fact(int n)
{
int i, f = 1;
for( i=1; i<=n; i++)
{
f *= i;
my_debug(MY_SECTION_FACT, 250)("i=%d ; f=%d/n", i, f);
}
return f;
}
int main(int argc, char *argv[])
{
if ( argc < 2 )
{
my_init_debug_levels(MY_SECTION_FACT, 0);
}
else
{
my_init_debug_levels(MY_SECTION_FACT, atoi(argv[1]));
}
printf( "4!=%d/n", fact(4) );
return 0;
}
程序9: fact.c