3.1数组
3.1.1数组概述
数组是Apache中最经常也是最普通的数据结构,尽管C语言中已经提供了一定的数组功能,但是C语言数组运用到Apache中还存在下面的一些问题:
(1)、C语言中的数组在定义的时候就必须确定维数目,一旦确定,其长度就不可更改。但是Apache中很多情况并不知道数组中能够保存的最大数目,如果预定义的太大,可能会浪费过多的空间;定义的太小又可能不能满足系统要求;因此Apache中需要一种动态的具有弹性伸缩能力的数组,这样实际需要多少元素就分配多少元素;
(2)、C语言中提供了另外一种数组动态伸缩的方案,即就是使用malloc分配空间,将空间看作数组进行处理。不过对于malloc分配的空间只能保存一种类型的数据。如果需要保存其余类型,则需要重新分配。
Apache中的数组实现方案类似于STL或者Java中的Vector,其长度可以保持动态变化,而且其中可以存放不同类型的数据,包括基本数据类型到复合数据类型。
3.1.2数组结构
Apache中相关的数组结构都定义在文件apr_table.h和apr_table.c中了。apr_array_header_t结构是Apache的核心结构,其定义如下:
struct apr_array_header_t {
apr_pool_t *pool;
int elt_size;
int nelts;
int nalloc;
char *elts;
};
数组的结构示意图片可以用下图表示:
![](http://asp.6to23.com/vcprogram/image/array0.png)
图2.1 Apache数组结构图
apr_array_header_t结构中的elts指针指向了实际的用malloc分配的空间,每个空间的大小为elt_size。由于数组元素的个数是可以动态变化的,因此结构中使用nalloc记录当前已经分配的数组中的元素个数。另外由于Apache中的数组元素并不是所有的都被用到,因此还需要nelts记录当前已经使用的元素个数,自然剩余的可以利用的数组元素个数为nalloc-nelts。分配数组所需要的所有空间都从内存池pool中获取。
3.1.3创建数组
数组的创建通过调用apr_array_make实现,不过其内部主要工作由make_array_core实现:
static void make_array_core(apr_array_header_t *res, apr_pool_t *p,int nelts, int elt_size, int clear)
{
if (nelts < 1) {
nelts = 1;
}
if (clear) {
res->elts = apr_pcalloc(p, nelts * elt_size);
}
else {
res->elts = apr_palloc(p, nelts * elt_size);
}
res->pool = p;
res->elt_size = elt_size;
res->nelts = 0; /* No active elements yet... */
res->nalloc = nelts; /* ...but this many allocated */
}
APR_DECLARE(apr_array_header_t *) apr_array_make(apr_pool_t *p,int nelts, int elt_size)
{
apr_array_header_t *res;
res = (apr_array_header_t *) apr_palloc(p, sizeof(apr_array_header_t));
make_array_core(res, p, nelts, elt_size, 1);
return res;
}
apr_array_make函数负责主要用于分配apr_array_head_t头结构,而make_array_core则主要负责创建和初始化res元素数组。Res数组的大小中每一个元素的大小为elt_size,总元素个数为nelts,因此分配的总空间为nelts*elt_size,空间分配后继续初始化apr_array_header_t结构中的成员。如果clear为1,则调用apr_pcalloc进行分配,否则调用apr_palloc,两者唯一的区别就是apr_pcalloc会对分配的空间进行初始化清零;而apr_palloc则不会初始化。
除了从头开始创建一个新的数组之外,APR中还允许使用apr_array_copy将一个数组拷贝到一个新的数组:
APR_DECLARE(apr_array_header_t *) apr_array_copy(apr_pool_t *p,const apr_array_header_t *arr)
Arr是需要拷贝的源数组,拷贝后的数组由函数返回。拷贝函数内部首先定义一个arp_array_header_t的变量res,从内存池p中为其分配足够的空间,然后调用make_array_core生成一个与arr数组大小相同的数组,再调用memcpy逐一进行内存拷贝。然后返回该空间。
3.1.4元素压入和弹出
Apache中对数组元素的操作类似于堆栈,只允许弹出(pop)和压入(push)两种,因此你无法象在C语言中随意的访问数组中的元素。
当数据需要保存到数组中的时候,通过函数apr_array_push函数进行,其原型声明如下:
APR_DECLARE(void *) apr_array_push(apr_array_header_t *arr)
arr是需要保存到数组中的元素。元素的保存按照从左到右,即从低索引到高索引依次存储,不可能出现C语言中的随机保存的情况,因此从这个角度而言,数组称之为堆栈更合适。
在将元素压入数组之前函数首先检查数组中是否有可用的空闲空间;如果有剩余可用空间,其将直接将元素拷贝到第nelts+1数组的位置,调整nelts为nelts++,同时返回该元素。如果没有,即意味着当前数据中nalloc个空间都已经被使用,即nelts=nalloc,那么此时必须扩大数组空间用来保存压入的数据。不过数组空间的增加并不是需要几个就增加几个,而是遵循下面的批量增加原则:
■ 如果压入数据之前nalloc为0,即尚未分配任何空间,那么此时只创建大小为elt_size的空间,即一个元素的空间。
■ 如果当前元素压入之前,系统分配的nalloc个单元都已经被使用完毕,那么此时将直接批量一次性将空间扩大到nalloc*2个元素空间。
一旦分配完这些空间,第一个可用的空闲空间则是第nalloc+1个元素。此时只需要将需要压入的元素拷贝到该地址空间即可,拷贝完之后,将elt_size加一,并返回新元素的地址。
与apr_array_push类似,Apache提供了另外的一个版本的压入函数apr_array_push_noclear,该函数与apr_array_push函数功能基本类似,其唯一区别正如函数名称,在于“noclear”。 apr_array_push在产生新的空闲块,压入新元素之前调用memset对新内存块进行清零操作,而“noclear”函数则省去了这一步。
与元素压入匹配,从数组中取元素通过函数apr_array_pop进行,其函数声明如下:
APR_DECLARE(void *) apr_array_pop(apr_array_header_t *arr)
元素弹出遵循先进先出的弹出原则,因此被弹出的元素永远都是第nelts个,除非当前的数组为空,不在有任何的有效数据。返回语句可以简化为return arr->elts + (arr->elt_size * (--arr->nelts));
3.1.5数组合并
Apache中使用apr_array_cat将两个元素进行合并,函数定义如下:
APR_DECLARE(void) apr_array_cat(apr_array_header_t *dst,const apr_array_header_t *src)
函数将数组src添加到数组dst的末尾。函数首先检查当前的空闲单元能不能容下src数组,这里容纳的概念并不是容纳src中的nalloc个元素,而仅仅是容纳下src中的nelts个非空闲单元,并不包括nalloc-nelts空闲单元,因此函数需要比较的是(dst->nalloc) - (dst->nelts) >= (src->nelts),而不是(dst->nalloc) - (dst->nelts) >= (src->nalloc)。如果能够容纳,则没有必要分配新的空间,直接内存拷贝就可以了。
如果dst中所有的空闲空单元都不够存放src中的空闲单元,那么此时毫无疑问dst需要分配新的空间,分配算法如下:
1)、如果dst中元素个数为零,此时,将产生一个新的空间。
2)、如果dst中元素个数nalloc不为零,则首先产生nalloc*2个空闲空间。
3)、尽管如此,如果src的非空闲元素实在太多,而dst本身空闲空间很小,那么即使一次产生 nalloc个空闲块也不一定能够盛放src中的元素。唯一的办法就是不停的产生新的空闲块,每次产生后的总块数都是当前的一倍,直到空闲块总数能够容纳src中的非空闲元素为止。这真是下面的代码所做的事情:
if (dst->nelts + src->nelts > dst->nalloc) {
int new_size = (dst->nalloc <= 0) ? 1 : dst->nalloc * 2;
while (dst->nelts + src->nelts > new_size) {
new_size *= 2;
}
}
一旦确定确定需要产生的空闲块的总数,函数将一次性从内存池dst->pool中申请。然后将src中的数据拷贝空间空间即可。
关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!
如果你觉得本文不错,请点击文后的“推荐本文”链接!!