printf記錄程序日誌,徹底告別vsnprintf

轉載自:點擊打開鏈接

通常我們需要在程序中輸出部分日誌信息,並把它記錄到文件中。在這種情況下,使用printf可以爲我們帶了很大方便。因爲printf卻省情況下是向stdout即控制檯屏幕輸出信息,在GUI程序中,我們看不到printf的輸出結果,但是我們可以將該輸出重定向到指定的文件中。即使用freopen(“c:\\yourlog.log”, “a+”,stdout)或通過yourapp.exe > c:\yourlog.log完成輸出重定向操作。
但是通常我們需要在記錄日誌的時候記錄更多的信息,比如說運行時間等,所以我們不能使用一條簡單的printf來完成該操作,另外,爲防止日誌信息以外丟失,我們最好是在每次printf後立即調用fflush。所以我們通常會使用下面的方法來完成日誌記錄操作:

void __cdecl log0 (const char* _Format, ...)
{
    char buff[10240], tm[80];
    va_list vl;
    va_start (vl, _Format);

    _strtime (tm);
    vsnprintf (buff, 10240, _Format, vl);
    printf ("%s - %s", tm, buff);
    fflush (stdout);
    va_end (vl);
}
從該代碼可以看出,我們必須事先定義好一個我們認爲足夠大的緩存已存儲所有可能的數據,這就是使用該方法帶來的inflexibility,究竟多大才算足夠大啊?10240?102400?甚至1024000?恐怕你的棧也沒這麼大吧!即使你在堆中分配存儲空間也一樣!
接下來我就介紹一種不用預先分配緩存並且能夠接受並輸出任意長度的信息至日至文件中(當然,只要不超過你的系統允許的大小),試想,只要我們在log0中完成了我們想做的任何事(比如輸出日誌前綴信息等),並且如果能夠將調用者傳遞給log0的參數“原封不動”的傳遞給printf的話,即將所有的可變參數按照printf所要求的格式傳遞給它,由它來完成剩下的操作。這是不是就克服了使用預先分配緩存的問題呢?沒錯,接下來所要解決的就是怎樣將這些可變參數“傳遞”給printf。
由於在log0內部,我們不知道調用者究竟傳遞了多少個參數進來,所以我們不能按照通常所用的按照參數名的方式將參數傳遞給printf。但是,先別急,看看log0的函數聲明,他是不是和printf的聲明完全一致呢(事實上只要是log0中的參數和printf中的部分一致也可,如void log1(char* filename, int len, char* _Format, ...)也可。)?
也就是說擁有相同聲明的函數,在被調用時,他們所擁有的參數棧(即Stack Frame)的結構是一樣的。所以,只要我們能夠從一個函數A“突然”跳轉到另外一個函數B中,那麼B所擁有的參數棧和A將是同一份數據,即他們“共享”了同一份參數棧數據。需要注意的是,這裏的跳轉不能使用通常的函數掉用來實現,因爲函數被調用時,編譯器會在背後做很多事情,如給我們設置新的ESP指針等等,因此這樣勢必不能達到共享參數棧數據的目的。爲了不讓編譯器在函數調用時在背後做任何事情,我們需要使用一個naked函數,在這樣的函數中我們就可以自己利用棧資源,自己控制所有一切。有了這樣的函數後,就可以很輕鬆,而且很高效的達到我們的目的了。

void mkprefix ()
{   char buff[80];
    _strtime (buff);
    printf ("%s - ", buff);
}

__declspec (naked)
void __cdecl
xprintf (const char* _Format, ...)
{
    __asm
    {   call    dword ptr [mkprefix]

        pop     ebx /* 將函數返回地址保存到EBX中 */
        call    dword ptr [printf]
        sub     esp, 4    /* 1 */

        /* 調用fflush將數據立即保存到文件中 */
        call    dword ptr [__iob_func]
        add     eax, 0x20
        push    eax
        call    dword ptr [fflush]
        add     esp, 4  /* 2 */

        mov     dword ptr [esp], ebx /* 恢複函數返回地址 */
        ret
    }
}
代碼中在1和2處分別對ESP減4後又加4,所以這兩處的代碼完全可以忽略,在這裏加上是爲了更好的理解函數調用的機制(即在函數調用後需要修正ESP,即所謂的Stack clean-up)。你可以將mkprefix 作的足夠複雜已記錄更多的信息,甚至我們可以通過log0將參數傳遞到mkprefix 中,向log1那樣。不過這樣處理起來就稍複雜點,爲簡單起見,就不再講述這種方法了。
當然,這只是這種所謂的“棧共享”技術的一個應用而已了,掌握了這種技術後,我想你肯定會把它應用到其他更適合的地方。
其實,在VC8中,由於提供了可變參數的宏,所以我們可以通過下面一條簡單的調用來完成日至記錄操作,而且信息還比較完全:
#define TRACE(fmt, ...) printf ("%s (%s:%d) - "##fmt, mkprefix(), __FILE__, __LINE__, __VA_ARGS__)

TRACE ("This is a debug information, a = %d, b = %s. ", 234, "xxxxx");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章