1. 程式人生 > >C語言中幾種輸出除錯資訊的方法

C語言中幾種輸出除錯資訊的方法

在除錯程式時,輸出除錯資訊是一種普遍、有效的方法。輸出除錯資訊一般有以下五種方法:

方法一:直接使用螢幕列印函式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

參考文獻: