[前言:使用__FILE__和__LINE__來(lái)定位錯(cuò)誤已經(jīng)屢見(jiàn)不鮮,然而其中一些道理又有幾個(gè)人仔細(xì)探究過(guò)。本文參考了Curtis Krauskopf的一篇名為
Using __FILE__ and __LINE__ to Report Errors的文章,希望達(dá)到解惑之效。]
問(wèn)題:當(dāng)運(yùn)行時(shí)錯(cuò)誤產(chǎn)生時(shí),我怎樣才能得到包含C++文件名和行號(hào)的字符串信息?
回答:在C++中的__FILE__預(yù)編譯指示器包含了被編譯的文件名,而__LINE__則包含了源代碼的行號(hào)。__FILE__和__LINE__的前后都包含了兩個(gè)下劃線,讓我們仔細(xì)看看__FILE__所包含的每個(gè)字符:
_ _ F I L E _ _
下面展示了在控制臺(tái)程序中如果顯示文件名和代碼行號(hào)。
#include <stdio.h>
int main(int , char**)
{
printf("This fake error is in %s on line %d\n", __FILE__, __LINE__);
return 0;
}
輸出結(jié)果:
This fake error is in c:\temp\test.cpp on line 5
讓我們更上一層樓
我想通過(guò)一個(gè)通用函數(shù)error()來(lái)報(bào)告錯(cuò)誤,以使當(dāng)某個(gè)錯(cuò)誤發(fā)生時(shí)我能設(shè)置斷點(diǎn)以及隔離錯(cuò)誤處理(例如,在屏幕上打印錯(cuò)誤信息或者寫入日志)。因此,函數(shù)的原型應(yīng)該是這樣的吧:
void error(const char *file, const unsigned long line, const char *msg);
調(diào)用方式如下:
error(__FILE__, __LINE__, "my error message");
預(yù)處理魔法
這里有三個(gè)問(wèn)題需要解決:
- __FILE__和__LINE__在每次調(diào)用error時(shí)作為參數(shù)傳入。
- __FILE和__LINE__前后的下劃線很容易被遺忘,從而導(dǎo)致編譯錯(cuò)誤。
- __LINE__是一個(gè)整數(shù),這無(wú)疑增加了error函數(shù)的復(fù)雜度。我絕不想直接使用整型的__LINE__,而通常都是將轉(zhuǎn)換為字符串打印到屏幕或?qū)懭肴罩疚募?br>
__FILE__和__LINE__應(yīng)該被自動(dòng)處理,而非每次作為參數(shù)傳遞給error,這樣會(huì)讓error的使用者感覺(jué)更爽些,它的形式可能是下面這樣:
error(AT, "my error message");
我希望上面的宏AT展開(kāi)為:"c:\temp\test.cpp:5"。而新的error函數(shù)則變成:
void error(const char *location, const char *msg);
因?yàn)锽orland C++ Builder編譯器能夠自動(dòng)合并相鄰的字符串,因此我把AT寫成下面這樣:
#define AT __FILE__ ":" __LINE__
然而它卻罷工了,因?yàn)開(kāi)_LINE__被擴(kuò)展成了整數(shù)而非字符串,所以宏展開(kāi)后變成:
"c:\temp\test.cpp" ":" 5
這是一個(gè)無(wú)效的字符串,因?yàn)槟┪灿幸粋€(gè)未被雙引號(hào)包含的整數(shù)。
怎么辦?別著急,一個(gè)特殊的預(yù)編譯指示器“#”能夠幫我們將一個(gè)變量轉(zhuǎn)換成字符串。所以重新定義宏:
#define AT __FILE__ ":" #__LINE__
嘿嘿,這樣總行了吧。別高興得太早,這樣也是不行的。因?yàn)榫幾g器會(huì)抱怨#是個(gè)無(wú)效字符。其實(shí),問(wèn)題是#預(yù)編譯指示器只有這樣使用才會(huì)
被正確識(shí)別:
#define symbol(X) #X
因此,我把代碼改為:
#define STRINGIFY(x) #x
#define AT __FILE__ ":" STRINGIFY(__LINE__)
然而,奇怪的結(jié)果產(chǎn)生了,__LINE__居然被作為了輸出的一部分:
c:\temp\test.cpp:__LINE__: fake error
解決方法是再用一個(gè)宏來(lái)包裝STRINGIFY():
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
OK,我們用下面的代碼來(lái)試試:
#include <stdio.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
void error(const char *location, const char *msg)
{
printf("Error at %s: %s\n", location, msg);
}
int main(int , char**)
{
error(AT, "fake error");
return 0;
}
輸出結(jié)果:
Error at c:\temp\test\test.cpp:11: fake error
Visual Studio下的實(shí)踐
在《Windows核心編程》中,Jeffrey Richter提供了下面的宏在編譯期輸出有用信息:
#define chSTR2(x) #x
#define chSTR(x) chSTR2(x)
#define chMSG(desc) message(__FILE__ "(" chSTR(__LINE__) "):" #desc)
message是一個(gè)預(yù)編譯指令,上面宏的使用方法是:
#pragma chMSG(Fix this later)
結(jié)論
- 預(yù)編譯指示器__FILE__和__LINE__能夠提供有用的信息。
- __LINE__的處理方式遠(yuǎn)比我們想象的要復(fù)雜。
- __FILE__被處理成字符串,給我們帶來(lái)了不少方便。