很多GNU C庫裡的函數都會偵測並報告錯誤條件。我們的程序需要檢測這些錯誤條件。比如:我們打開一個輸入文件時需要判斷該文件是否正確的打開。如果沒有正確打開,我們需要打印錯誤或者采取其他正確的方式。為了利用這種錯誤報告機制,我們需要包含頭文件errno.h
很多庫函數都會返回一個特殊的值來顯示函數運行出錯。常見的特殊值有:-1、空指針、EOF常量。但是這些返回值只能告訴你有錯誤產生,但不會告訴你錯誤是什麼。如果你想知道錯誤是什麼,就得靠錯誤代碼,錯誤代碼存儲在變量errno中(在errno.h中有聲明)
errno變量包含了系統錯誤代碼,其類型是volatile。該類型意味著其可以突然被異步線程改變,編譯器從不假設其值。如果你在寫信號處理程序應當保存改變量的值並恢復其值。
errno的初始值為0,遇到錯誤時,errno絕無可能為0。但沒有錯誤的時候,errno也不一定為0(庫函數在成功運行時並不會修改errno的值)。所以,不要依據errno的值來判斷錯誤是否發生。正確的做法是為每一個函數做好文檔,標注出錯誤代碼的值對應的錯誤類型。這樣調用失敗時,你可以通過檢查errno獲取錯誤代碼,然後查詢函數文檔獲取錯誤詳情。如果你想獲取某一庫函數的錯誤代碼,最好再次之前設置errno為0(或許你還想先保存以下errno的值,然後便於恢復該值)。
每一個錯誤代碼都有一個以E開頭緊跟大寫字母或數字的符號名,實際為定義在errno.h中的宏。當然不是所有的宏都定義在一個errno.h中(詳細的可以自己翻一翻頭文件,注意不僅僅只有一個errno.h,多個errno.h共同定義了全部的宏)
錯誤代碼的值一般為正數並且都不相同,但也有一個例外:EWORLDBLOCK和EAGAIN的錯誤代碼是一樣的。除了EWORLDBLOCK和EAGAIN,你可以使用switch語句來判斷錯誤代碼。但你不應該依賴於此,你唯一可以相信的就是文檔。
除了GNU/Hurd系統,幾乎所有的系統調用被傳入一個無效指針時都會返回EFAULT。所以呢,glibc的函數庫說明文檔中往往會省略對EFAULT的解釋。
大多數的錯誤代碼宏名都顯而易見的好懂,如果實在不知其意可以查看手冊或者
~# man errno
這裡簡單提幾個宏:
Macro: int EDOM 域錯誤,可以理解為定義域錯誤。主要用在數學函數中。如果數學函數的一個參數值不在函數定義域中,則會將errno設置為EDOM Macro: int ERANGE 范圍錯誤,與上面的EDOM恰好相反。EDOM是定義域的話,ERANGER就是值域了。也多用於數學函數中。如果數學函數返回值超過了約定的返回,則會將errno設置為ERANGE Macro: int EAGAIN 資源暫時不可獲得。這種錯誤可能是隨機的,你再次運行的時候便好了。。。EWOULDBLOCK是EAGAIN的一個別名。
我們知道錯誤代碼,但總覺得查文檔不方便。幸好庫文件給我們提供了錯誤消息報告函數。這些函數可以報告一個具有說明性的錯誤消息。部分消息報告函數我們可以自己定義消息格式。
函數strerror和perror為每一個錯誤代碼都提供了一個標准的錯誤消息。而變量program_invocation_short_name則可以方便獲取程序的名字,告訴我們哪個程序出錯。
幾個函數原型:
#include <string.h> char * strerror(int errnum); char *strerror_r(int errnum, char *buf, size_t n);
說明: strerror和strerror_r兩個函數差不多。區別在於安全性,官方文檔對strerror的注釋是MT-Unsafe race:strerror,而strerror_r則為MT-Safe。strerror返回一個靜態申請的字符串緩沖區,該緩沖區被所有線程共享。而strerror_r返回的是一個私有副本,並不被其他線程共享。另外這兩個函數都有可能造成內存溢出(靜態申請的緩沖區)。盡管strerror_r可以指定字符串長度,但這長度是char *buf的。這函數有兩個返回值,一個使用reurn返回,還有一個是char *buf。return返回的依舊是一個靜態緩沖區。
#include<stdio.h> void perror(const char *message);
說明: perror將error message打印到標准錯誤輸出中。如果你傳進的參數是一個空指針,perror會根據errno打印錯誤消息。如果char *message非空,perror會將message當作錯誤消息的前綴輸出。perror得立即調用,不然errno的值可能發生變化。
char *program_invocation_name; //等同於argv[0] char *program_invocation_short_name //不包含目錄名。
說明: 這兩個變量的初始化工作由glibc庫在還未調用main函數之前執行。所以在非GNU庫中,這兩個變量不起效果,在實際代碼中我們需要定義_GNU_SOURCE宏,告訴編譯器使用GNU庫。
以下兩個函數在整個GNU project中使用非常廣泛。
void error(int status, int errnum, const char *format, ...); void error_at_line(int status, int errnum, const char *fname, unsigned int lineno, const char *format, ...);
說明: 這兩個函數的返回和status有關,如果status是0,則正常格式化打印錯誤消息。全局變量error_message_count也會做自增操作。錯誤消息的格式如下:program_name: format_string: error_messager_for_errno\n。如果status非零,這兩個函數將調用exit status,即以狀態status退出(不會返回)。關於program_name:全局變量error_print_progname指向的函數決定了program_name的值。error_at_line函數有點特別:多了fname,lineno兩個參數。錯誤消息格式如下:program_name:fname:lineno format_string: error_mesage_for_errno\n。如果全局變量error_one_per_line被設置為非零值,每一行只會打印一個錯誤消息。
除了以上的錯誤消息函數,我們還有以下幾個:這幾個函數主要用在BSD系統中,定義在頭文件err.h中,在gnu系統中不推薦使用。
void warn(const char *format, ...) void vwarn(const char *format, va_list ap) void warnx(const char *format, ...) void vwarnx(const char *format, va_list ap) void err(int status, const char *format, ...) void verr(int status, const char *format, va_list ap) void errx(int status, const char *format, ...) void verrx(int status, const char *format, va_list ap)
各位看官自行查看手冊吧。just be a man!!!