除了人工的分析之外,最簡單最直接的調試方法要算printf了。不過,我們這裏推薦使用的並不是初學C語言時使用的函數int printf(const char *format, ...),而是稍微複雜一點的fprintf()函數,因爲它更方便我們之後重定向錯誤輸出信息到指定的設備。fprintf()函數的原型如下:
int fprintf(FILE *stream, const char *format, ...) |
可以看到,它與printf()函數相比多出來了第一個參數FILE *stream,其意義是將打印的內容輸出到文件流指針stream指向的流。所謂流,通常是指程序輸入或輸出的一個連續的字節序列,設備(例如鼠標、鍵盤、磁盤、屏幕、調制解調器和打印機)的輸入和輸出都是用流來處理的,在C語言中,所有的流均以文件的形式出現——不一定是物理磁盤文件,還可以是對應於某個輸入/輸出源的邏輯文件。C語言提供了5種標準的流,你的程序在任何時候都可以使用它們,並且不必打開或關閉它們。以下列出了這5種標準的流。
------------------------------------------------
名稱 描 述 例 子
------------------------------------------------
stdin 標準輸入 鍵盤
stdout 標準輸出 屏幕
stderr 標準錯誤 屏幕
stdprn 標準打印機 LPT1端口
stdaux 標準串行設備 COM1端口
------------------------------------------------
其中,stdprn和stdaux並不總是預先定義好的,因爲LPT1和COM1端口在某些操作系統中是沒有意義的,而stdin,stdout 和stderr總是預先定義好的。此外,stdin並不一定來自鍵盤,stdout也並不一定顯示在屏幕上,它們都可以重定向到磁盤文件或其它設備上。我們在頭文件stdio.h中可以找到stdin,stdout 和stderr的定義如下:
------------------------------------------------
名稱 描 述 例 子
------------------------------------------------
stdin 標準輸入 鍵盤
stdout 標準輸出 屏幕
stderr 標準錯誤 屏幕
stdprn 標準打印機 LPT1端口
stdaux 標準串行設備 COM1端口
------------------------------------------------
其中,stdprn和stdaux並不總是預先定義好的,因爲LPT1和COM1端口在某些操作系統中是沒有意義的,而stdin,stdout 和stderr總是預先定義好的。此外,stdin並不一定來自鍵盤,stdout也並不一定顯示在屏幕上,它們都可以重定向到磁盤文件或其它設備上。我們在頭文件stdio.h中可以找到stdin,stdout 和stderr的定義如下:
/* Standard streams. */ extern struct _IO_FILE *stdin; /* Standard input stream. */ extern struct _IO_FILE *stdout; /* Standard output stream. */ extern struct _IO_FILE *stderr; /* Standard error output stream. */ |
在使用fprintf()函數時,通常我們可以將第一個參數設爲stdout或者stderr,打印出錯調試信息的時候則推薦使用stderr而不是stdout,這是一種慣例,同時也由於內核在處理stdout和stderr時的優先級不一樣,後者的優先級要高一些,因此有時候如果程序異常退出時,stderr能得到輸出,而stdout就不行。
printf(...) 實際上相當於fprintf(stdout, ...),這也是爲什麼我們不推薦使用它的原因。在輸出調試信息的時候,我們推薦使用fprintf(stderr, …),或者使用某個指定的文件流fprintf(some_stream, …)。
那麼具體如何在必要的時候重定向fprintf()中的調試信息呢?來看看下面的一些方法:
當調試信息的量比較大,需要一些時間或者其他輔助工具來搜索過濾時,僅僅利用顯示屏幕來輸出調試信息是不夠的,這時我們經常將這些信息輸出到所謂的日誌文件(log)中,之後再仔細的分析log文件來發現問題。
Ø 利用Shell的I/O重定向
簡單的寫log方法可以通過shell的I/O重定向機制來實現,比如下面的代碼:
1 #include <stdio.h> 2 3 int main() 4 { 5 fprintf(stdout, "This is a standard output info!\n"); 6 fprintf(stderr, "This is a standard error output info!\n"); 7 return 0; 8 } |
在默認條件下,編譯運行的結果是打印信息都輸出在屏幕上:
$ gcc fprint.c -o fprint $ ./fprint This is a standard output info! This is a standard error output info! |
這是因爲默認情況下,shell所打開的stdout和stderr設備都是顯示屏幕。不過我們可以通過shell的重定向功能來將打印信息寫到文件中去。比如:
$ ./fprint >output.log This is a standard error output info! $ cat output.log This is a standard output info! |
這樣,我們把stdout的輸出寫到了文件output.log中,不過stderr的輸出還是在屏幕上。如何重定向stderr呢?這需要用到shell定義的文件描述符。在shell下stdin, stdout, 和stderr的文件描述符分別是0, 1和2,我們可以用下面的方法重定向:
$ ./fprint >output.log 2>error.log $ cat output.log This is a standard output info! $ cat error.log This is a standard error output info! $ $ ./fprint >output.log 2>&1 $ cat output.log This is a standard error output info! This is a standard output info! |
其中./fprint >output.log 2>error.log分別將stdout和stderr的輸出寫入到文件output.log和error.log中,而./fprint >output.log 2>&1則表示將stderr的輸出追加到stdout的文件output.log中(結果是output.log中既有stdout輸出也有stderr輸出)。
一些常用的shell I/O語法如下:
cmd > file 把 stdout 重定向到 file 文件中
cmd >> file 把 stdout 重定向到 file 文件中(追加)
cmd 1> fiel 把 stdout 重定向到 file 文件中
cmd > file 2>&1 把 stdout 和 stderr 一起重定向到 file 文件中
cmd 2> file 把 stderr 重定向到 file 文件中
cmd 2>> file 把 stderr 重定向到 file 文件中(追加)
cmd >> file 2>&1 把 stderr 和 stderr 一起重定向到 file 文件中(追加)
cmd >> file 把 stdout 重定向到 file 文件中(追加)
cmd 1> fiel 把 stdout 重定向到 file 文件中
cmd > file 2>&1 把 stdout 和 stderr 一起重定向到 file 文件中
cmd 2> file 把 stderr 重定向到 file 文件中
cmd 2>> file 把 stderr 重定向到 file 文件中(追加)
cmd >> file 2>&1 把 stderr 和 stderr 一起重定向到 file 文件中(追加)
在平時的簡單調試中,我們可以靈活利用這些方法來快速得到log文件。
Ø 用freopen()進行重定向
有時候我們要求在程序中能夠控制標準流的重定向,這時可以利用標準C庫函數freopen()。freopen()的函數原型如下:
FILE *freopen(const char *filename, const char *mode, FILE *stream) |
下面的代碼用來測試用函數freopen()重定向stderr:
1 #include <stdio.h> 2 3 int main() 4 { 5 if (freopen("err.log", "w", stderr)==NULL) 6 fprintf(stderr, "error redirecting stderr\n"); 7 fprintf(stdout, "This is a standard output info!\n"); 8 fprintf(stderr, "This is a standard error output info!\n"); 9 fclose(stderr); 10 return 0; 11 } |
在第5行我們用freopen()函數將stderr重定向到了”err.log”文件,這樣得到的結果如下:
$ gcc print_log.c -o print_log $ ./print_log This is a standard output info! $ cat err.log This is a standard error output info! |
可見第8行打印到stderr的信息被重定向到了err.log文件中,而第7行stdout的打印信息則還是輸出到了屏幕上