簡(jiǎn)介:
頭文件errno.h定義了一個(gè)全局的宏errno,它被展開為一個(gè)int類型的“左值”,這意味著宏errno不一定是個(gè)對(duì)象的標(biāo)識(shí)符,也可以展開為一個(gè)由函數(shù)返回的可以修改的“左值”,比如int*errno(),這個(gè)后面會(huì)講,你可以暫且把它理解為一個(gè)全局的int型變量(雖然這樣理解是錯(cuò)的,不過(guò)方便理解)。
簡(jiǎn)單來(lái)說(shuō),errno.h只是為了提供了一種錯(cuò)誤報(bào)告機(jī)制。比如,一個(gè)函數(shù)調(diào)用fopen()發(fā)生了錯(cuò)誤,它可能就會(huì)去修改errno的值,這樣外部的代碼可以通過(guò)判斷errno的值來(lái)區(qū)分fopen()內(nèi)部執(zhí)行時(shí)是否發(fā)生錯(cuò)誤,并根據(jù)errno值的不同來(lái)確定具體的錯(cuò)誤類型。
先來(lái)看一段代碼,Demo1:
04 | int main ( int argc, char *argv[] ) |
07 | FILE *fp = fopen ( "whatever.txt" , "r" ); |
11 | printf ( "Can not open file\n" ); |
13 | printf ( "errno value: %d, it means: %s" , errno , strerror ( errno )); |
程序會(huì)輸出:
2 | errno value: 2, it means: No such file or directory |
strerror是標(biāo)準(zhǔn)庫(kù)stdio.h定義的一個(gè)函數(shù),它用來(lái)返回錯(cuò)誤代碼所代表的含義。如Demo1所示,我們用fopen(也在stdio.h中定義)打開一個(gè)并不存在的文件,因此返回的fp是一個(gè)空指針。而在fopen嘗試打開文件失敗時(shí)會(huì)修改errno的值,Demo1里fopen失敗原因是文件不存在,因此fopen將會(huì)把errno指向的值修改為2,通過(guò)stderror可以看到錯(cuò)誤代碼2的意思是“No suchfile or directory”。
帥氣,看起來(lái)用errno來(lái)報(bào)告錯(cuò)誤,既方便,也很簡(jiǎn)單,但實(shí)際應(yīng)用時(shí)遠(yuǎn)沒(méi)Demo1那么簡(jiǎn)單,試下Demo2:
05 | int main ( int argc, char *argv[] ) |
08 | FILE *fp = fopen ( "whatever.txt" , "r" ); |
12 | printf ( "Can not open file\n" ); |
14 | int root = sqrt (123568 -123668 ); |
16 | printf ( "errno value: %d, it means: %s" , errno , strerror ( errno )); |
程序會(huì)輸出:
2 | errno value: 33, it means: Numerical argument out of domain |
這跟我們期望的錯(cuò)誤信息完全不一樣,因?yàn)閟qrt函數(shù)在得到一個(gè)非法的參數(shù)時(shí),它把errno的值修改成了33,覆蓋了fopen設(shè)置的錯(cuò)誤代碼。所以,使用errno一個(gè)比較安全的編碼方式是,在下個(gè)庫(kù)函數(shù)調(diào)用之前就查看它的值,千萬(wàn)不要因?yàn)槟硞€(gè)庫(kù)函數(shù)看似很簡(jiǎn)單就假設(shè)它不會(huì)修改errno的值,那樣會(huì)死的很慘……如果必須在查看errno之前調(diào)用別的庫(kù)函數(shù),一種安全的方式是先把errno的值保存到一個(gè)臨時(shí)變量里,然后調(diào)用那個(gè)“必須”調(diào)用的庫(kù)函數(shù),處理完畢后再把errno恢復(fù)到之前的值。如Demo3:
05 | double getSqrt( double value) |
12 | double root = sqrt (123568 -123668 ); |
14 | printf ( "I changed errno to '%d' sliently...but it's safe \n" , errno ); |
22 | int main ( int argc, char *argv[] ) |
24 | FILE *fp = fopen ( "whatever.txt" , "r" ); |
28 | printf ( "Can not open file\n" ); |
32 | printf ( "errno value: %d, it means: %s" , errno , strerror ( errno )); |
程序會(huì)輸出:
2 | I changed errno to '33' sliently...but it's safe |
3 | errno value: 2, it means: No such file or directory |
現(xiàn)在,就像期望的那樣輸出了。
但是……
這樣真的安全么?!想像一下,如果errno是個(gè)全局變量,那多線程環(huán)境下豈不完蛋了?!本來(lái)線程A把errno設(shè)置成2,還沒(méi)執(zhí)行到查看錯(cuò)誤的語(yǔ)句時(shí),線程B就把errno設(shè)置成了33,然后線程A才開始查看errno并輸出錯(cuò)誤信息,而這時(shí)輸出的錯(cuò)誤就很讓人抓狂了!神呀,這破東西多線程沒(méi)法兒用哇!
但是……
你多慮了……文章開始說(shuō)過(guò),宏errno可以被展開為一個(gè)“左值”,比如int*getYourErrno(),所以你可以在getYourErrno()里返回一個(gè)線程內(nèi)的局部變量,這樣不管哪個(gè)線程修改errno都修改的它自己的局部變量,所以我們擔(dān)心的問(wèn)題是不存在的??聪耬rrno.h的源碼就明白了
03 | #include <bits/errno.h> |
上面的注釋說(shuō)了,如果errno沒(méi)有定義過(guò)就把errno定義為“extern interrno;”,如果這樣多線程時(shí)是會(huì)發(fā)生悲劇的,先不著急哭,我們?nèi)デ懊婵纯此欠癖欢x過(guò),前面的代碼include了一個(gè)叫bits/errno.h的頭文件,看名字就很“險(xiǎn)惡”,進(jìn)去看看:
3 | extern int *__errno_location ( void ) __THROW __attribute__ ((__const__)); |
5 | # if !defined _LIBC || defined _LIBC_REENTRANT |
7 | # define errno (*__errno_location ()) |
9 | # endif /* !__ASSEMBLER__ */ |
果然來(lái)者不善……如果沒(méi)定義宏__ASSEMBLER__,就會(huì)執(zhí)行中間的代碼,里面的代碼又說(shuō)了,如果你沒(méi)定義_LIBC或者定義了_LIBC_REENTRANT他們就會(huì)把errno定義為__errno_location,一看到宏_LIBC_REENTRANT里面有“reentrant(重入)”就知道它不是個(gè)好東西……不是,是看到它的名字就知道它是跟多線程有關(guān)的,所以如果要在多線程環(huán)境下正確的使用errno,你需要確保__ASSEMBLER__沒(méi)有被定義,而且_LIBC沒(méi)被定義或者定義了_LIBC_REENTRANT。
可以寫個(gè)程序看下自己開發(fā)環(huán)境里這幾個(gè)宏的設(shè)置:
07 | printf ( "__ASSEMBLER__ is NOT defined!\n" ); |
09 | printf ( "__ASSEMBLER__ is defined!\n" ); |
13 | printf ( "__LIBC is NOT defined\n" ); |
15 | printf ( "__LIBC is defined!\n" ); |
18 | #ifndef _LIBC_REENTRANT |
19 | printf ( "_LIBC_REENTRANT is NOT defined\n" ); |
21 | printf ( "_LIBC_REENTRANT is defined!\n" ); |
我的輸出:
1 | _ASSEMBLER__ is NOT defined! |
3 | _LIBC_REENTRANT is NOT defined! |
哈!看來(lái)我可以在多線程下安全的使用errno,如果你的默認(rèn)環(huán)境不可以就在makefile里定義上_LIBC_REENTRANT吧!
ok,有關(guān)errno.h的介紹到此就結(jié)束,休息,休息一下!
作者:
Yao 來(lái)源:
http://blog.yaohuiji.com/ 歡迎轉(zhuǎn)載!作者期望轉(zhuǎn)載時(shí)帶上原文鏈接,不過(guò)這不是必須的。但務(wù)必在文章標(biāo)題處標(biāo)明【轉(zhuǎn)載】
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。