Skip to main content

[筆記] System Call的錯誤處理

Opass
A life well lived

檢查錯誤是非常好的習慣,因為你在未來的某一天,很可能會因為沒有養成這個習慣,花費數十個小時在抓一個「這應該不大可能出錯」的System Call上。因此在使用System Call時,請養成檢查錯誤的習慣。 每次系統呼叫失敗的時候,通常會回傳一個特殊的值,絕大多數的情況下是-1,或是NULL,極少數情形是其他特殊的值。這些回傳值只是告訴你:你已經死了,至於你是怎麼死的,他們會留下驗屍的線索,也就是errno。 一開始程式啟動時,errno是0,代表一切正常。但隨著程式出現錯誤,errno會被設成非0的值,代表錯誤的代碼。透過檢查這個代碼,可以得知是哪種類型的死因,就可以有個驗屍的方向。 標準的驗屍流程是這樣的:

  1. 檢查回傳值,看看自己是不是死掉了
  2. 如果死掉了,檢查errno驗屍

關於errno,有以下幾條規則

  • 成功的System Call或Library function不會將errno設成0
  • 但成功的System Call可以將errno設成其他非0值(這是SUSv3允許的,儘管很少人會這麼做)
  • 連續兩次的System Call,後面的System Call可能會覆蓋掉前面的errno

用來印出錯誤訊息的System Call有兩個,分別是perror和strerror

perror

#include <stdio>
void perror(const char *msg);

perror會印出自訂的msg字串,然後加一個分號和空白 : ,然後直接印出該errno所代表的錯誤訊息到stderr上。 通常自訂的msg字串會是剛剛使用的系統呼叫,這樣比較好除錯。 範例:

fd = open(pathname, flags, mode);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}

strerror()

strerror()會接收錯誤代碼,並回傳錯誤訊息的字串指標,這可以讓我們方便印出更格式化的錯誤訊息。

#include <string.h>
char *strerror(int errnum);

範例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
printf ("Error opening file unexist.ent: %s\n",strerror(errno));
return 0;
}

有一點要注意的是,因為實作上回傳的字串所使用的空間可能永遠是同一個,因此連續兩次sterror的呼教可能會把第一次呼叫的字串洗掉。

特殊情況

  • getpriority()的成功回傳值可能是-1,因此要檢查錯誤時,你必須先把errno設成0,呼叫完之後進行檢查看看是否有出錯。
  • 極少數的System Call永遠不會出錯,可以不用檢查。像是getpid(), _exit()

我要如何得知errno數字所代表的錯誤名稱?

這是研究這段時產生的疑問,我能不能透過errno反查錯誤名稱(例如EOVERFLOW),而不只是印出錯誤字串。答案是,可以但是非常麻煩,你不會想要這麼做。請參閱這則stackoverflow上的問題How can I print the symbolic name of an error in c 如果你真的想知道錯誤訊息是哪個錯誤名稱,可以直接man errno去對照比較快。

資料來源