一、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使用線程變量的方式值得借鑒。