“傳遞/轉(zhuǎn)發(fā)”可變參數(shù)并通過(guò)printf記錄程序日志,徹底告別vsnprintf!:)
通常我們需要在程序中輸出部分日志信息,并把它記錄到文件中。在這種情況下,使用printf可以為我們帶了很大方便。因?yàn)?span lang="EN-US">printf卻省情況下是向stdout即控制臺(tái)屏幕輸出信息,在GUI程序中,我們看不到printf的輸出結(jié)果,但是我們可以將該輸出重定向到指定的文件中。即使用freopen(“c:\\yourlog.log”, “a+”,stdout)或通過(guò)yourapp.exe > c:\yourlog.log完成輸出重定向操作。
但是通常我們需要在記錄日志的時(shí)候記錄更多的信息,比如說(shuō)運(yùn)行時(shí)間等,所以我們不能使用一條簡(jiǎn)單的printf來(lái)完成該操作,另外,為防止日志信息以外丟失,我們最好是在每次printf后立即調(diào)用fflush。所以我們通常會(huì)使用下面的方法來(lái)完成日志記錄操作:
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);
}
從該代碼可以看出,我們必須事先定義好一個(gè)我們認(rèn)為足夠大的緩存已存儲(chǔ)所有可能的數(shù)據(jù),這就是使用該方法帶來(lái)的inflexibility,究竟多大才算足夠大啊?10240?102400?甚至1024000?恐怕你的棧也沒(méi)這么大吧!即使你在堆中分配存儲(chǔ)空間也一樣!
接下來(lái)我就介紹一種不用預(yù)先分配緩存并且能夠接受并輸出任意長(zhǎng)度的信息至日至文件中(當(dāng)然,只要不超過(guò)你的系統(tǒng)允許的大小),試想,只要我們?cè)?span lang="EN-US">log0中完成了我們想做的任何事(比如輸出日志前綴信息等),并且如果能夠?qū)⒄{(diào)用者傳遞給log0的參數(shù)“原封不動(dòng)”的傳遞給printf的話,即將所有的可變參數(shù)按照printf所要求的格式傳遞給它,由它來(lái)完成剩下的操作。這是不是就克服了使用預(yù)先分配緩存的問(wèn)題呢?沒(méi)錯(cuò),接下來(lái)所要解決的就是怎樣將這些可變參數(shù)“傳遞”給printf。
由于在log0內(nèi)部,我們不知道調(diào)用者究竟傳遞了多少個(gè)參數(shù)進(jìn)來(lái),所以我們不能按照通常所用的按照參數(shù)名的方式將參數(shù)傳遞給printf。但是,先別急,看看log0的函數(shù)聲明,他是不是和printf的聲明完全一致呢(事實(shí)上只要是log0中的參數(shù)和printf中的部分一致也可,如void log1(char* filename, int len, char* _Format, ...)也可。)?
也就是說(shuō)擁有相同聲明的函數(shù),在被調(diào)用時(shí),他們所擁有的參數(shù)棧(即Stack Frame)的結(jié)構(gòu)是一樣的。所以,只要我們能夠從一個(gè)函數(shù)A“突然”跳轉(zhuǎn)到另外一個(gè)函數(shù)B中,那么B所擁有的參數(shù)棧和A將是同一份數(shù)據(jù),即他們“共享”了同一份參數(shù)棧數(shù)據(jù)。需要注意的是,這里的跳轉(zhuǎn)不能使用通常的函數(shù)掉用來(lái)實(shí)現(xiàn),因?yàn)楹瘮?shù)被調(diào)用時(shí),編譯器會(huì)在背后做很多事情,如給我們?cè)O(shè)置新的ESP指針等等,因此這樣勢(shì)必不能達(dá)到共享參數(shù)棧數(shù)據(jù)的目的。為了不讓編譯器在函數(shù)調(diào)用時(shí)在背后做任何事情,我們需要使用一個(gè)naked函數(shù),在這樣的函數(shù)中我們就可以自己利用棧資源,自己控制所有一切。有了這樣的函數(shù)后,就可以很輕松,而且很高效的達(dá)到我們的目的了。
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 /* 將函數(shù)返回地址保存到EBX中 */
call dword ptr [printf]
sub esp, 4 /* 1 */
/* 調(diào)用fflush將數(shù)據(jù)立即保存到文件中 */
call dword ptr [__iob_func]
add eax, 0x20
push eax
call dword ptr [fflush]
add esp, 4 /* 2 */
mov dword ptr [esp], ebx /* 恢復(fù)函數(shù)返回地址 */
ret
}
}
代碼中在1和2處分別對(duì)ESP減4后又加4,所以這兩處的代碼完全可以忽略,在這里加上是為了更好的理解函數(shù)調(diào)用的機(jī)制(即在函數(shù)調(diào)用后需要修正ESP,即所謂的Stack clean-up)。你可以將mkprefix 作的足夠復(fù)雜已記錄更多的信息,甚至我們可以通過(guò)log0將參數(shù)傳遞到mkprefix 中,向log1那樣。不過(guò)這樣處理起來(lái)就稍復(fù)雜點(diǎn),為簡(jiǎn)單起見(jiàn),就不再講述這種方法了。
當(dāng)然,這只是這種所謂的“棧共享”技術(shù)的一個(gè)應(yīng)用而已了,掌握了這種技術(shù)后,我想你肯定會(huì)把它應(yīng)用到其他更適合的地方。
其實(shí),在VC8中,由于提供了可變參數(shù)的宏,所以我們可以通過(guò)下面一條簡(jiǎn)單的調(diào)用來(lái)完成日至記錄操作,而且信息還比較完全:
#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");
posted on 2009-08-12 13:01 肥仔 閱讀(835) 評(píng)論(0) 編輯 收藏 引用 所屬分類(lèi): C++ 基礎(chǔ)

