侵入式单链表的简单实现
众所周知,一个普通的单链表看起来是这样子滴,
typedef struct foo_s { int data; struct foo_s *next; } foo_t;
结构体里包含了一个指向同类型的链表指针next; 而侵入式单链表则不同,让结构体包含一个成员变量,该成员变量是一个通用的链表结点。看起来是这个样儿滴,
typedef struct list_s { struct list_s *next; } list_t; typedef struct foo_s { int data; list_t link; } foo_t;
所有包含了list_t link的结点构成一个单链表。如果说传统的单链表看起来是这样的,(背景为绿色:数据域,背景为白色:链接域;背景为灰色:结点的内存首地址)
那么,侵入式单链表看起来则是这样的,
于是,普通的单链表和侵入式单链表的区别在于:
- 普通的单链表的结点链接域存的是下一个结点的内存首地址
- 侵入式单链表的结点链接域存的是下一个结点的链接域成员变量的内存首地址
前一节我们详细分析了offsetof, typeof和container_of, 下面给出一个最简单的侵入式单链表实现。
1. list.h
1 #ifndef _LIST_H 2 #define _LIST_H 3 4 #ifdef __cplusplus 5 extern "C" { 6 #endif 7 8 /** 9 * offsetof - offset of a structure member 10 * @TYPE: the type of the struct. 11 * @MEMBER: the name of the member within the struct. 12 */ 13 #define offsetof(TYPE, MEMBER) ((size_t)(&(((TYPE *)0)->MEMBER))) 14 15 /** 16 * container_of - cast a member of a structure out to the containing structure 17 * @ptr: the pointer to the member. 18 * @type: the type of the container struct this is embedded in. 19 * @member: the name of the member within the struct. 20 * 21 */ 22 #define container_of(ptr, type, member) ({ \ 23 const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 24 (type *)( (char *)__mptr - offsetof(type, member) );}) 25 26 typedef struct list_s { 27 struct list_s *next; 28 } list_t; 29 30 typedef void (*list_handler_t)(void *arg); 31 32 extern void list_init(list_t **head, list_t *node); 33 extern void list_fini(list_t *head, list_handler_t fini); 34 extern void list_show(list_t *head, list_handler_t show); 35 36 #ifdef __cplusplus 37 } 38 #endif 39 40 #endif /* _LIST_H */
2. list.c
1 /* 2 * Generic single linked list implementation 3 */ 4 #include <stdio.h> 5 #include "list.h" 6 7 void 8 list_init(list_t **head, list_t *node) 9 { 10 static list_t *tail = NULL; 11 12 if (*head == NULL) { 13 *head = tail = node; 14 return; 15 } 16 17 tail->next = node; 18 tail = node; 19 node->next = NULL; 20 } 21 22 void 23 list_show(list_t *head, list_handler_t show) 24 { 25 for (list_t *p = head; p != NULL; p = p->next) 26 show(p); 27 } 28 29 void 30 list_fini(list_t *head, list_handler_t fini) 31 { 32 list_t *p = head; 33 while (p != NULL) { 34 list_t *q = p; 35 p = p->next; 36 fini(q); 37 } 38 }
3. foo.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "list.h" 4 5 typedef struct foo_s { 6 int data; 7 list_t link; 8 } foo_t; 9 10 static void 11 foo_show(void *arg) 12 { 13 list_t *q = (list_t *)arg; 14 foo_t *p = container_of(q, foo_t, link); 15 16 printf("show (list) %p next (list) %p \t: " 17 "show (node) %p = {0x%x, %p}\n", 18 q, p->link.next, p, p->data, p->link.next); 19 } 20 21 static void 22 foo_fini(void *arg) 23 { 24 list_t *q = (list_t *)arg; 25 foo_t *p = container_of(q, foo_t, link); 26 27 foo_t *next_nodep = NULL; 28 if (p->link.next != NULL) 29 next_nodep = container_of(p->link.next, foo_t, link); 30 31 printf("free (node) %p next (node) %p\n", p, next_nodep); 32 p->link.next = NULL; 33 free(p); 34 } 35 36 int 37 main(int argc, char *argv[]) 38 { 39 if (argc != 2) { 40 fprintf(stderr, "Usage: %s <num>\n", argv[0]); 41 return -1; 42 } 43 44 list_t *head = NULL; 45 for (int i = 0; i < atoi(argv[1]); i++) { 46 foo_t *p = (foo_t *)malloc(sizeof (foo_t)); 47 if (p == NULL) /* error */ 48 return -1; 49 p->data = 0x1001 + i; 50 51 printf("init (node) %p\n", p); 52 list_init(&head, &p->link); 53 } 54 55 list_show(head, foo_show); 56 57 list_fini(head, foo_fini); 58 59 return 0; 60 }
4. Makefile
CC = gcc CFLAGS = -g -Wall -m32 -std=gnu99 all: foo foo: foo.o list.o ${CC} ${CFLAGS} -o $@ $^ foo.o: foo.c ${CC} ${CFLAGS} -c $< list.o: list.c list.h ${CC} ${CFLAGS} -c $< clean: rm -f *.o clobber: clean rm -f foo cl: clobber
5. 编译并运行
$ make gcc -g -Wall -m32 -std=gnu99 -c foo.c gcc -g -Wall -m32 -std=gnu99 -c list.c gcc -g -Wall -m32 -std=gnu99 -o foo foo.o list.o $ ./foo 3 init (node) 0x88a1008 init (node) 0x88a1018 init (node) 0x88a1028 show (list) 0x88a100c next (list) 0x88a101c : show (node) 0x88a1008 = {0x1001, 0x88a101c} show (list) 0x88a101c next (list) 0x88a102c : show (node) 0x88a1018 = {0x1002, 0x88a102c} show (list) 0x88a102c next (list) (nil) : show (node) 0x88a1028 = {0x1003, (nil)} free (node) 0x88a1008 next (node) 0x88a1018 free (node) 0x88a1018 next (node) 0x88a1028 free (node) 0x88a1028 next (node) (nil)
小结: 在类型为foo_t的结构体中包含了成员变量list_t link, 那么根据link.next的值(本质上是内存地址)就能使用container_of()计算出结构体变量的内存首地址。一旦拿到了结构体变量的内存首地址,访问其内容就易如反掌。但是,本文中给出的实现是让list.c/list.h的消费者foo.c直接使用container_of(), 例如list_show()在访问具体的node里的data的时候是通过callback的方式,因此,list.c离真正的侵入式单链表实现还是有距离滴。欲知后事如何,且听下回分解。