1. 程式人生 > >【C/C++開發】除錯printf, fprintf

【C/C++開發】除錯printf, fprintf

標準C只支援可變引數的函式 ,意味著函式的引數是不固定的,例如printf()函式
的原型為:
int printf( const char *format [, argument]... );
而在GNU C中,巨集也可以接受可變數目的引數,例如
#define  pr_debug(fmt,arg...) \
printk(fmt,##arg)
這裡arg 表示其餘的引數可以是零個或多個,這些引數以及引數之間的逗號構成
arg 的值,在巨集擴充套件時替換arg,例如下列程式碼:
pr_debug("%s:%d",filename,line)

會被擴充套件為:

printk("%s:%d", filename, line)


我建議你的輸出流是: stdout   核心是用printk作為輸出 一般C語言是用fprintf 微控制器可能用vfprintf #undef PDEBUG             /* undef it, just in case */
#ifdef SBULL_DEBUG
#  ifdef __KERNEL__
     /* This one if debugging is on, and kernel space */
#    define PDEBUG(fmt, args...) printk( KERN_DEBUG "sbull: " fmt, ## args)
#  else
     /* This one for user space */
#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#  endif
#else
#  define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#undef PDEBUGG
#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */
 
          重要的參考資料:   #include <stdio.h>
#include <unistd.h>

int main(void)
{
    int i = 0;
    while(1) {
        printf("sleeping %d", i++); //(1) 
        fflush(stdout)
 
;
        sleep(1);
    }
    return 0;
}

(1) printf將"sleeping %d"輸出到標準輸出檔案的緩衝區中(緩衝區在記憶體上),fflush(stdout)將緩衝區中的內容強制重新整理到,並將其中的內容輸出到顯示器上("/n"回車換行 == fflush(stdout)+換行)
                            fflush()
        buffer(In memroy) -----------> hard disk/monitor

(2) 有三個流(
 stream )是自動開啟的, 
相應的FILE結構指標為stdin、stdout、 stderr ,與之對應的檔案描述符是:STDIN _ FILENO、STDOUT_ FILENO、STDERR _ FILENO 

流緩衝的屬性:
緩衝區型別有:全緩衝(大部分緩衝都是這型別)、行緩衝(例如stdio,stdout)、無緩衝(例如stderr )。
關於全緩衝,例如普通的檔案操作,進行fputs、fprintf操作後,資料並沒有立即寫入磁碟檔案中,當fflush或fclose檔案時,資料才真正寫入。
可以用以下函式設定流的緩衝型別:
void setvbuf() void setbuf()
void setbuffer() void setlinebuf()

(3)
fflush() 是把 FILE *裡的緩衝區(位於使用者態程序空間)重新整理到核心中
fsync() - 是把核心中對應的緩衝(是在 vfs 層的緩衝)重新整理到硬碟中
 

(4) 在Linux的標準函式庫中,有一套稱作“高階I/O”的函式,我們熟知的printf()、fopen()、fread()、fwrite()都在此 列,它們也被稱作“緩衝I/O(buffered I/O)”,每次寫檔案的時候,也僅僅是寫入記憶體中的緩衝區,等滿足了一定的條件(達到一定數量,或遇到特定字元,如換行符/n和檔案結束符EOF),再 將緩衝區中的內容一次性寫入檔案,這樣就大大增加了檔案讀寫的速度。




-----------------------------------------------------------------
    The three types of buffering available are unbuffered block buffered , and line buffered . When an output stream is unbuffered, information appears on the destination file or terminal as soon as written; when it is block buffered many characters are saved up and written as a block; when it is line buffered characters are saved up until a newline is output or input is read from any stream attached to a terminal device (typically stdin). The function fflush(3) may be used to force the block out early. (See fclose(3).) Normally all files are block buffered. When the first I/O operation occurs on a file, malloc(3) is called, and a buffer is obtained. If astream refers to a terminal (as stdout normally does) it is line buffered. The standard error
stream stderr is always unbuffered by default.

    一般來說,block buffered的效率高些,將多次的操作合併成一次操作。先在標準庫裡快取一部分,直到該緩衝區滿了,或者程式顯示的呼叫fflush時,將進行更新操作。而setbuf 則可以設定該緩衝區的大小。


setbuf()
----------------------------------------------------
    #include <stdio.h>
    void setbuf(FILE *stream , char *buf);
    這個函式應該必須在如何輸出被寫到該檔案之前呼叫。一般放在main裡靠前面的語句!但是setbuf有個經典的錯誤,man手冊上也提到了,c陷阱和缺陷上也提到了
    You must make sure that both buf and the space it points to still exist by the time streamis closed, which also happens at program termination. For example, the following is illegal:
       #include <stdio.h>
       int main()
       {
           char buf[BUFSIZ];
           setbuf(stdin, buf);
           printf("Hello, world!/n");
           return 0;
       }
    這個程式是錯誤的。buf緩衝區最後一次清空應該在main函式結束之後,程式交回控制給作業系統之前C執行庫所必須進行的清理工作的一部分,但是此時buf字元陣列已經釋放。 修改的方法是將buf設定為static,或者全域性變數; 或者呼叫malloc來動態申請記憶體。
    char * malloc();
    setbuf(stdout,malloc(BUFSIZE));
    這裡不需要判斷malloc的返回值,如果malloc呼叫失敗,將返回一個null指標,setbuf的第二個引數可以是null,此時不進行緩衝!

fflush()
----------------------------------------------------
    fflush函式則重新整理緩衝區,將緩衝區上的內容更新到檔案裡。
    #include <stdio.h>
    int fflush(FILE *stream );

    The function fflush forces a write of all user-space buffered data for the given output or update stream via the stream underlying write function. The open status of the stream is unaffected. If the stream argument is NULL, fflush flushes all open output streams.
    但是fflush僅僅重新整理C庫裡的緩衝。其他的一些資料的重新整理需要呼叫fsync或者sync!
    Note that fflush() only flushes the user space buffers provided by the C library. 
To ensure that the data is physically stored on disk the kernel buffers must be flushed too, e.g. with sync(2) or fsync(2).

fsync()和sync()
----------------------------------------------------
    fsync和sync最終將緩衝的資料更新到檔案裡。
    #include <unistd.h>
    int fsync(int fd);
    fsync copies all in-core parts of a file to disk, and waits until the device reports that all parts are on stable storage. It also updates metadata stat information. It does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync on the file descriptor of the directory is also needed.
    同步命令sync就直接呼叫了sync函式來更新磁碟上的緩衝!

轉處:http://blog.chinaunix.net/u/22754/showart_652802.html

除了人工的分析之外,最簡單最直接的除錯方法要算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的定義如下:

/* 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檔案來發現問題。

Ø       利用ShellI/O重定向

簡單的寫log方法可以通過shellI/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所開啟的stdoutstderr裝置都是顯示螢幕。不過我們可以通過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定義的檔案描述符。在shellstdin, stdout, stderr的檔案描述符分別是0, 12,我們可以用下面的方法重定向:

$ ./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分別將stdoutstderr的輸出寫入到檔案output.logerror.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 檔案中(追加)

在平時的簡單除錯中,我們可以靈活利用這些方法來快速得到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的列印資訊則還是輸出到了螢幕上。