c 语言并未提供像 c++ 一样的 std 标准库,所以很多类似 list、变长数组、map 等常见的数据结构都需要自己实现。
链表使用指针将不同的内存地址相互关联,往往用于管理不连续内存,相对于数组,其内存管理更加灵活。今天让我们先看一下 nginx 中单向链表 list 的实现。
结构体与接口
typedef struct ngx_list_part_s ngx_list_part_t;
// ngx_list_part_s 是一片连续内存,ngx_list_t 将多个 ngx_list_part_s 链接起来
// 也就是说 ngx_list_t 中的 node 其实是 ngx_list_part_s
struct ngx_list_part_s {
void *elts; // c 没有模版。只能通过 void * 来模拟模版
ngx_uint_t nelts; // 分配的元素的数量
ngx_list_part_t *next; // next node
};
typedef struct {
ngx_list_part_t *last; // ngx_list_part_s 链表的最后一个节点
ngx_list_part_t part; // ngx_list_part_s 链表的头元素
size_t size; // ngx_list_t 中元素的大小
ngx_uint_t nalloc; // ngx_list_t 中元素个数
ngx_pool_t *pool; // 其对应的内存池对象
} ngx_list_t;
// 创建一个 ngx_list_t
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
// 插入一个元素
void *ngx_list_push(ngx_list_t *list);
ngx_list_t 本身其实是通过单向链表的形式来管理一个个的 ngx_list_part_s 结构体,ngx_list_part_s 则管理一片连续的内存(大小一但申请就不会变化)。每当调用 ngx_list_push 插入元素到 ngx_list_t 中,就会从 ngx_list_part_s 的空间中分配。
创建
/*
创建一个 ngx_list_t,其中 n 为每个 ngx_list_part_s 中元素的个数,size 为这些元素的内存大小
*/
ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
ngx_list_t *list;
// 使用内存池创建一个 ngx_list_t
list = ngx_palloc(pool, sizeof(ngx_list_t));
if (list == NULL) {
return NULL;
}
// 为 ngx_list_part_s 链表中的 head 元素申请内存
list->part.elts = ngx_palloc(pool, n * size);
if (list->part.elts == NULL) {
return NULL;
}
list->part.nelts = 0;
list->part.next = NULL;
list->last = &list->part;
list->size = size;
list->nalloc = n;
list->pool = pool;
return list;
}
插入元素
/*
插入元素到链表中
*/
void *
ngx_list_push(ngx_list_t *l)
{
void *elt;
ngx_list_part_t *last;
// ngx_list_part_s 链表的最后一个元素
last = l->last;
// 如果 ngx_list_part_s 中已经没有剩余空间
if (last->nelts == l->nalloc) {
// 构造一个新 ngx_list_part_s 对象
last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}
// 申请 ngx_list_part_s 存放对象的内存
last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}
last->nelts = 0;
last->next = NULL;
// 将新的 ngx_list_part_s 插入到链表尾端
l->last->next = last;
l->last = last;
}
// 在 ngx_list_part_s 中分配一个元素
elt = (char *) last->elts + l->size * last->nelts;
last->nelts++;
return elt;
}
总结
不同于常见的 list,ngx_list_t 中每个 node 是一个一片的连续内存,可以理解为一个个数组通过 next 指针相连。这样做可以降低单次插入元素的成本,但是却存在一定程度上的浪费空间。