一、errno的由來
在C編程中,errno是個不可缺少的變數,特別是在網路編程中。如果你沒有用過errno,那隻能說明你的程式不夠健壯。當然,如果你是WIN32平台的GetLastError(),效果也是一樣的。
為什麼會使用errno呢?個人認為,這是系統庫設計中的一個無奈之舉,他更多的是個技巧,而不是架構上的需要。我們觀察下函數結構,可以發現,函數的參數傳回值只有一個,這個傳回值一般可以攜帶錯誤資訊,比如負數表示錯誤,而正數表述正確的傳回值,比如recv函數。但是對於一些返回指標的函數,如: char *get_str();這個方法顯然沒有用的。NULL可以表示發生錯誤,但是發生什麼錯誤卻毫無辦法。於是,errno就誕生了。全域變數errno可以存放錯誤原因,當錯誤發生時,函數的傳回值是可以通過非法值來提示錯誤的發生。
二、errno的安全執行緒
errno是全域變數,但是在多線程環境下,就會變得很恐怖。當你調用一個函數時,發現這個函數發生了錯誤,但當你使用錯誤原因時,他卻變成了另外一個線程的錯誤提示。想想就會覺得是件可怕的事情。
將errno設定為線程局部變數是個不錯的主意,事實上,GCC中就是這麼乾的。他保證了線程之間的錯誤原因不會互相串改,當你在一個線程中串列執行一系列過程,那麼得到的errno仍然是正確的。
看下,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__ */
而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. */
#ifndef errno
extern int errno;
#endif
顯然,errno實際上,並不是我們通常認為的是個整型數值,而是通過整型指標來擷取值的。這個整型就是安全執行緒的。
三、errno的實現
static pthread_key_t key;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
static void make_key()
{
(void) pthread_key_create(&key, NULL);
}
int *_errno()
{
int *ptr ;
(void) pthread_once(&key_once, make_key);
if ((ptr = pthread_getspecific(key)) == NULL)
{
ptr = malloc(sizeof(int));
(void) pthread_setspecific(key, ptr);
}
return ptr ;
}
四、errno的應用
errno在庫中得到廣泛的應用,但是,錯誤編碼實際上不止那麼多。我們需要在自己的系統中增加更多的錯誤編碼。一種方式就是直接利用errno,另外一種方式就是定義自己的user_errno。
使用errno,strerror可能無法解析,這需要自己解決。但errno使用線程變數的方式值得借鑒。