struct __per_type_pool_policy
在繼續深入到__common_pool_base和其他pool類裏面之前,我好奇的看了下__per_type_pool_policy的定義,發現一些事情,於是決定先說說它。
545 /// @brief Policy for individual __pool objects.
546 template<typename _Tp, template <bool> class _PoolTp, bool _Thread>
547 struct __per_type_pool_policy
548 : public __per_type_pool_base<_Tp, _PoolTp, _Thread>
__per_type_pool_policy的原型,除了名字,它與__common_pool_policy相比還少了一個模板參數_Tp,這正是它與分配對象類型相關的原因。
549 {
550 template<typename _Tp1, template <bool> class _PoolTp1 = _PoolTp,
551 bool _Thread1 = _Thread>
552 struct _M_rebind
553 { typedef __per_type_pool_policy<_Tp1, _PoolTp1, _Thread1> other; };
這裏就是我發現的“一些事情”:__per_type_pool_policy::_M_rebind::other是與_Tp1類型相關的,即不同的_Tp1類型會實例化出不同類型的other(__per_type_pool_policy),那麼這裏我們看到了__common_pool_policy:: _M_rebind存在的意義——與其他的policy保持接口一致。接口一致意味着我們可以不用修改代碼,就隨意替換不同的policy。
555 using __per_type_pool_base<_Tp, _PoolTp, _Thread>::_S_get_pool;
556 using __per_type_pool_base<_Tp, _PoolTp, _Thread>::_S_initialize_once;
557 };
struct __common_pool_base
__mt_alloc的類層次結構太多了,即使到了__common_pool_base這裏,還只是一個概念性的“封裝”類。不過有一點進步的是,它開始處理多線程情況了。
395 template<template <bool> class _PoolTp, bool _Thread>
396 struct __common_pool_base;
這是__common_pool_base的申明,第一個模板參數_PoolTp是實際的內存池類,第二個參數_Thread表示是否支持多線程。接着分別針對_Thread爲false和true的情況,進行了偏特化實現。__common_pool_base最重要的作用就是實現_S_initialize_once,即第一次調用allocate時需要進行的初始化工作。下面是_Thread = false情況下的實現:
404 static void
405 _S_initialize_once()
406 {
407 static bool __init;
靜態變量__init記錄是否進行過初始化。根據C++標準,全局或局部靜態變量會自動初始化爲默認值,而bool的默認值爲false。所以此處即使沒有明確寫出__init的初始值,它也會被初始化爲false。
408 if (__builtin_expect(__init == false, false))
除了第一次調用_S_initialize_once會發現__init爲false外,其他時候是不會有“__init == false”的。
409 {
410 _S_get_pool()._M_initialize_once();
411 __init = true;
把真正的內存池進行參數初始化,然後把__init修改爲true。
412 }
413 }
由於是在單線程情況下,任何代碼都是按照順序執行的,所以這裏不需要任何的加鎖與解鎖操作。
而在多線程情況下,事情會變複雜些。
423 static void
424 _S_initialize()
425 { _S_get_pool()._M_initialize_once(); }
輔助靜態函數_S_initialize的作用是包裝內存池的非靜態成員函數_M_initialize_once,以便在下面的_S_initialize_once函數裏作爲一般函數指針傳遞給__gthread_once。
427 static void
428 _S_initialize_once()
429 {
430 static bool __init;
431 if (__builtin_expect(__init == false, false))
上面的代碼和單線程下是一樣的,不需解釋。
432 {
433 if (__gthread_active_p())
434 {
435 // On some platforms, __gthread_once_t is an aggregate.
436 static __gthread_once_t __once = __GTHREAD_ONCE_INIT;
437 __gthread_once(&__once, _S_initialize);
438 }
這段代碼裏出現了幾個我們極少見到的單詞,__gthread_active_p,__gthread_once_t,__GTHREAD_ONCE_INIT和__gthread_once。我覺得等到介紹完整個函數後,再回頭介紹這些難點,會比較好。
440 // Double check initialization. May be necessary on some
441 // systems for proper construction when not compiling with
442 // thread flags.
443 _S_get_pool()._M_initialize_once();
444 __init = true;
無論上面平臺相關的代碼最後的處理結果是什麼,最後還是調用一次內存池的_M_initialize_once,保證初始化工作的正確運行。
445 }
446 }
__gthread_active_p
那麼現在回到432-438之間的代碼,它們的主要作用是處理各平臺之間對鎖的實現與定義的差異。由於我也只是在一種平臺下研究這份源碼,所以我覺得還是專注於某種流行的平臺下的代碼比較好。如果想要“一口吞掉整個大象”,最終可能一無所獲。
我所在的環境是Linux 2.6內核,GCC版本爲4.1.2,使用POSIX多線程庫,其他選項默認。那麼我們開始接觸第2個源代碼文件“/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../include/c++/4.1.2/i386-redhat-linux/bits/gthr-default.h”,以下簡稱爲gthr-default.h。在gthr-default.h裏,__gthread_active_p()最終變成了如下代碼:
<gthr-default.h>
149 static inline int
150 __gthread_active_p (void)
151 {
152 static void *const __gthread_active_ptr
153 = __extension__ (void *) &__gthrw_(pthread_cancel);
上面定義了一個靜態的const指針__gthread_active_ptr,初步猜測是指向了一個函數地址,這個函數與pthread_cancel有關係。熟悉POSIX線程的讀者肯定知道pthread_cancel是POSIX線程函數中的一員,那麼這裏其實就是在檢測這些函數是否可用。
154 return __gthread_active_ptr != 0;
這句話佐證了我們的初步猜測。
155 }
__extension__
首先我們看看__extension__關鍵字。以下是在《The Definitive Guide to GCC》上找到的一段解釋:
當使用gcc加-pedantic選項來編譯C語言程序的時候,可以通過-std=version來指定代碼應該符合什麼版本的語言標準。但是,通過-pedantic(或它的擴展-pedantic-errors)來查看你的程序是否嚴格符合ISO C語言標準,是錯誤的做法,即使你加上-ansi選項也不行。因爲這些選項只會在標準“需要警告”的地方產生警告信息——絕對嚴格的ISO語法分析器將產生龐大的警告信息。所以,-pedantic的目的是檢測出“無理由”的非兼容代碼,禁用GNU擴展,禁用未列入標準的C++或傳統C語言特徵。
另一個不能用-pedantic來檢測是否兼容ISO標準的原因是,-pedantic會忽略那些以下劃線開頭和結尾的可選關鍵字,以及在__extension__關鍵字之後的語句。有一條原則是,這2種例外情況不能發生在應用程序代碼裏,因爲可選關鍵字和__extension__關鍵字都是給系統頭文件使用的,應用程序絕對不能使用它們。
既然__extension__只是爲了防止編譯器的警告信息,那麼我們可以專注於後面的__gthrw_了。
__gthrw_
首先,在我的環境下:
<gthr-default.h>
68 # define __gthrw_(name) __gthrw_ ## name
說明一下,在C語言宏定義裏,##是連接的意思,即A##B表示AB;而#則表示字符串轉義,即#A表示”A”。那麼__gthrw_(pthread_cancel)就會變成__gthrw_pthread_cancel。那麼__gthrw_pthread_cancel又是什麼呢?我找到了如下代碼:
<gthr-default.h>
62 # ifndef __gthrw_pragma
63 # define __gthrw_pragma(pragma)
64 # endif
65 # define __gthrw2(name,name2,type) /
66 extern __typeof(type) name __attribute__ ((__weakref__(#name2))); /
67 __gthrw_pragma(weak type)
<gthr-default.h>
81 #define __gthrw3(name) __gthrw2(__gthrw_ ## name, __ ## name, name)
82 __gthrw3(pthread_once)
83 __gthrw3(pthread_getspecific)
84 __gthrw3(pthread_setspecific)
85 __gthrw3(pthread_create)
86 __gthrw3(pthread_cancel)
87 __gthrw3(pthread_mutex_lock)
88 __gthrw3(pthread_mutex_trylock)
89 __gthrw3(pthread_mutex_unlock)
90 __gthrw3(pthread_mutex_init)
首先__gthrw3(pthread_cancel)會變成:
__gthrw2(__gthrw_pthread_cancel, pthread_cancel, pthread_cancel)
然後又變成了:
extern __typeof(pthread_cancel) __gthrw_pthread_cancel __attribute__ ((__weakref__("pthread_cancel")));
typeof
那麼,上面的代碼究竟是什麼意思呢?好吧,還是老辦法:逐個擊破!
typeof類似於sizeof,它返回一個類型,即後面所接的表達式的類型,而__typeof是和typeof同意義的可選關鍵字。舉幾個例子:
typedef typeof (int *) IntPtr; ——IntPtr表示int指針類型;
int * x;
typeof(x) y; ——y和x一樣是int指針類型變量;
typeof(*x) z; ——z是一個int類型變量;
typeof(&x) t; ——t是int **類型變量;
好了,現在__typeof(pthread_cancel)就很容易理解了。pthread_cancel的原型爲:
int pthread_cancel(pthread_t thread);
所以__typeof(pthread_cancel)就表示這樣的一個函數指針類型,後面的__gthrw_pthread_cancel則是變量名。
__attribute__
__attribute__關鍵字需要和別的關鍵字搭配,用來說明代碼的屬性,比如函數屬性,類型屬性,變量屬性等,如果你想深入研究,可以看看http://gcc.gnu.org/onlinedocs/gcc/Keyword-Index.html#Keyword-Index。我這裏只關係它和後面的__weakref__搭配時的意義。
__weakref__
__attribute__ ((weakref ("target")));
等價於:
__attribute__ ((weak, weakref, alias ("target ")));
第一個weak表示:申明的符號是一個弱符號(weak symbol),而非全局符號,主要用於那些能被用戶代碼重載的庫函數申明裏,當然它也能用於申明非函數符號。
第二個weakref 表示:這個符號是一個弱引用(weak reference)。弱引用就是一個別名(alias),本身不需要任何定義,弱引用需要指定目標符號(後面的alias部分)。在靜態可執行文件裏,弱引用符號會被轉換成絕對符號(absolute symbols),並賦值爲0;在動態鏈接的可執行文件或共享的目標文件裏,弱引用是沒有定義的,賦初值爲0。在執行過程中,鏈接器會搜索目標符號,如果找到的話,把弱引用綁定到目標符號上;如果沒有找到,把弱引用綁定到地址0,不產生任何運行時錯誤消息。
歷史上,未定義的弱引用被用來測試某個函數是否存在。
OK,列出了這麼多的介紹,最終我們得出一個結論:gthr-default.h文件第152,153行申明瞭一個名爲__gthread_active_ptr的指針,並用名爲__gthrw_pthread_cancel的弱引用爲其賦值。__gthrw_pthread_cancel在鏈接時會搜索名爲pthread_cancel的函數,如果找到,那麼賦值爲函數地址;如果沒有找到,賦值爲0。於是__gthread_active_ptr最終要麼等於函數pthread_cancel的地址,要麼等於0,取決於鏈接的庫裏是否包含了這個函數。
如此複雜的代碼只是爲了得到這樣簡單的結果,實在讓人覺得感嘆!我開始明白那句20%與80%的諺語了。
回到多線程下__common_pool_base::_S_initialize_once函數裏,__gthread_once_t最終的類型是pthread_once_t,__GTHREAD_ONCE_INIT最終的值是0,它們都是POSIX多線程庫的一部分。__gthread_once的定義爲:
<gthr-default.h>
516 static inline int
517 __gthread_once (__gthread_once_t *once, void (*func) (void))
518 {
519 if (__gthread_active_p ())
520 return __gthrw_(pthread_once) (once, func);
根據我們以前的分析,最後一句話會變成:return __gthrw_pthread_once (once, func);
如果pthread_once函數存在的話,會調用pthread_once(once,func);否則什麼也不會做,也不會有運行時錯誤產生。pthread_once函數是POSIX多線程庫的一部分。
521 else
522 return -1;
523 }
至此我們就研究完了這個“深奧”的多線程下__common_pool_base::_S_initialize_once函數。現在當你再看它的代碼時,你是否對每一句代碼都瞭然於胸了?