第三篇:errno.h快速入門

來源:互聯網
上載者:User

簡介:

標頭檔errno.h定義了一個全域的宏errno,它被展開為一個int類型的“左值”,這意味著宏errno不一定是個對象的標識符,也可以展開為一個由函數返回的可以修改的“左值”,比如int *errno(),這個後面會講,你可以暫且把它理解為一個全域的int型變數(雖然這樣理解是錯的,不過方便理解)。

 

簡單來說,errno.h只是為了提供了一種錯誤報表機制。比如,一個函數調用fopen()發生了錯誤,它可能就會去修改errno的值,這樣外部的代碼可以通過判斷errno的值來區分fopen()內部執行時是否發生錯誤,並根據errno值的不同來確定具體的錯誤類型。
先來看一段代碼,Demo1:

#include<stdio.h>#include<errno.h>int main ( int argc, char *argv[] ){//try to open file "whatever.txt". when you run this demo,make sure the file is NOT existed..。FILE *fp = fopen("whatever.txt","r");if(fp ==NULL){printf("Can not open file\n");printf("errno value: %d, it means: %s",errno, strerror(errno));}return 0;}

程式會輸出:

Can not open fileerrno value: 2, it means: No such file or directory

strerror是標準庫stdio.h定義的一個函數,它用來返回錯誤碼所代表的含義。如Demo1所示,我們用fopen(也在stdio.h中定義)開啟一個並不存在的檔案,因此返回的fp是一個null 指標。而在fopen嘗試開啟檔案失敗時會修改errno的值,Demo1裡fopen失敗原因是檔案不存在,因此fopen將會把errno指向的值修改為2,通過stderror可以看到錯誤碼2的意思是“No such file or directory”。

帥氣,看起來用errno來報告錯誤,既方便,也很簡單,但實際應用時遠沒Demo1那麼簡單,試下Demo2:

#include<stdio.h>#include<errno.h>#include<math.h>int main ( int argc, char *argv[] ){/*try to open file "whatever.txt". when you run this demo,make sure the file is NOT existed...*/FILE *fp = fopen("whatever.txt","r");if(fp ==NULL){printf("Can not open file\n");int root = sqrt(123568 -123668 );printf("errno value: %d, it means: %s",errno, strerror(errno));}return 0;}

程式會輸出:

Can not open fileerrno value: 33, it means: Numerical argument out of domain

這跟我們期望的錯誤資訊完全不一樣,因為sqrt函數在得到一個非法的參數時,它把errno的值修改成了33,覆蓋了fopen設定的錯誤碼。所以,使用errno一個比較安全的編碼方式是,在下個庫函數調用之前就查看它的值,千萬不要因為某個庫函數看似很簡單就假設它不會修改errno的值,那樣會死的很慘……如果必須在查看errno之前調用別的庫函數,一種安全的方式是先把errno的值儲存到一個臨時變數裡,然後調用那個“必須”調用的庫函數,處理完畢後再把errno恢複到之前的值。如Demo3:

#include<stdio.h>#include<errno.h>#include<math.h>double getSqrt(double value){//1、save last errno to a temp variableint tmpErrno = errno;//2、set errno to 0errno = 0;double root = sqrt(123568 -123668 );printf("I changed errno to '%d' sliently...but it's safe \n",errno);//3.restore errnoerrno = tmpErrno;return root;}int main ( int argc, char *argv[] ){FILE *fp = fopen("whatever.txt","r");if(fp ==NULL){printf("Can not open file\n");getSqrt(-1);printf("errno value: %d, it means: %s",errno, strerror(errno));}return 0;}

程式會輸出:

Can not open fileI changed errno to '33' sliently...but it's safeerrno value: 2, it means: No such file or directory

現在,就像期望的那樣輸出了。
但是……
這樣真的安全嗎?!想像一下,如果errno是個全域變數,那多線程環境下豈不完蛋了?!本來線程A把errno設定成2,還沒執行到查看錯誤的語句時,線程B就把errno設定成了33,然後線程A才開始查看errno並輸出錯誤資訊,而這時輸出的錯誤就很讓人抓狂了!神呀,這破東西多線程沒法兒用哇!
但是……
你多慮了……文章開始說過,宏errno可以被展開為一個“左值”,比如int* getYourErrno(),所以你可以在getYourErrno()裡返回一個線程內的局部變數,這樣不管哪個線程修改errno都修改的它自己的局部變數,所以我們擔心的問題是不存在的。看下errno.h的源碼就明白了

/* Get the error number constants from the system-specific file.   This file will test __need_Emath and _ERRNO_H.  */#include <bits/errno.h>#undef__need_Emath#ifdef_ERRNO_H/* Declare the `errno' variable, unless it's defined as a macro by   bits/errno.h.  This is the case in GNU, where it is a per-thread   variable.  This redeclaration using the macro still works, but it   will be a function declaration without a prototype and may trigger   a -Wstrict-prototypes warning.  */#ifndeferrnoextern int errno;#endif

上面的注釋說了,如果errno沒有定義過就把errno定義為“extern int errno;”,如果這樣多線程時是會發生悲劇的,先不著急哭,我們去前面看看它是否被定義過,前面的代碼include了一個叫bits/errno.h的標頭檔,看名字就很“險惡”,進去看看:

# ifndef __ASSEMBLER__/* Function to get address of global `errno' variable.  */extern int *__errno_location (void) __THROW __attribute__ ((__const__));#  if !defined _LIBC || defined _LIBC_REENTRANT/* When using threads, errno is a per-thread value.  */#   define errno (*__errno_location ())#  endif# endif /* !__ASSEMBLER__ */

果然來者不善……如果沒定義宏__ASSEMBLER__,就會執行中間的代碼,裡面的代碼又說了,如果你沒定義_LIBC或者定義了_LIBC_REENTRANT他們就會把errno定義為__errno_location,一看到宏_LIBC_REENTRANT裡面有“reentrant(重入)”就知道它不是個好東西……不是,是看到它的名字就知道它是跟多線程有關的,所以如果要在多線程環境下正確的使用errno,你需要確保__ASSEMBLER__沒有被定義,而且_LIBC沒被定義或者定義了_LIBC_REENTRANT。
可以寫個程式看下自己開發環境裡這幾個宏的設定:

#include <stdio.h>#include <errno.h>int main( void ){#ifndef __ASSEMBLER__        printf( "__ASSEMBLER__ is NOT defined!\n" );#else        printf( "__ASSEMBLER__ is defined!\n" );#endif#ifndef __LIBC        printf( "__LIBC is NOT defined\n" );#else        printf( "__LIBC is defined!\n" );#endif#ifndef _LIBC_REENTRANT        printf( "_LIBC_REENTRANT is NOT defined\n" );#else        printf( "_LIBC_REENTRANT is defined!\n" );#endif        return 0;}

我的輸出:

_ASSEMBLER__ is NOT defined!__LIBC is NOT defined!_LIBC_REENTRANT is NOT defined!

哈!看來我可以在多線程下安全的使用errno,如果你的預設環境不可以就在makefile裡定義上_LIBC_REENTRANT吧!
ok,有關errno.h的介紹到此就結束,休息,休息一下!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.