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函数。现在当你再看它的代码时,你是否对每一句代码都了然于胸了?