前一节介绍的侵入式链表实现在封装性方面做得不好,因为会让消费者foo.c直接使用宏container_of()。这一节对list的定义做了一点改进,如下所示:
typedef struct list_s { struct list_s *next; size_t offset; } list_t;
既然链表结点已经保存了offset, 那么就不再需要宏container_of()了。(注:Solaris的侵入式双向循环链表就是这么玩的,跟Linux玩法不一样。)
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 typedef struct list_s { 16 struct list_s *next; 17 size_t offset; 18 } list_t; 19 20 extern list_t *list_d2l(void *object, size_t offset); 21 extern void *list_l2d(list_t *list); 22 23 #ifdef __cplusplus 24 } 25 #endif 26 27 #endif /* _LIST_H */
- list_d2l(): 根据数据(data)结点的内存首地址得到侵入式链表(list)结点的内存首地址。
- list_l2d(): 根据侵入式链表(list)结点的内存首地址得到数据(data)结点的内存首地址。
2. list.c
1 /* 2 * Generic single linked list implementation 3 */ 4 #include <stdio.h> 5 #include "list.h" 6 7 list_t * 8 list_d2l(void *object, size_t offset) 9 { 10 if (object == NULL) 11 return NULL; 12 13 list_t *p = (list_t *)((char *)object + offset); 14 p->offset = offset; 15 p->next = NULL; 16 17 return p; 18 } 19 20 void * 21 list_l2d(list_t *list) 22 { 23 if (list == NULL) 24 return NULL; 25 26 return (void *)((char *)list - list->offset); 27 }
- list_d2l(): 侵入式链表结点的内存首地址 = 数据结点的内存首地址 + offset
- list_l2d(): 数据结点的内存首地址 = 侵入式链表结点的内存首地址 - offset
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_init(list_t **head, void *object, size_t offset) 12 { 13 if (object == NULL) 14 return; 15 16 printf("init (node) %p\n", object); 17 list_t *node = list_d2l(object, offset); 18 19 if (*head == NULL) { 20 *head = node; 21 return; 22 } 23 24 list_t *tail = NULL; 25 for (list_t *p = *head; p != NULL; p = p->next) 26 tail = p; 27 tail->next = node; 28 } 29 30 static void 31 foo_fini(list_t *head) 32 { 33 list_t *p = head; 34 while (p != NULL) { 35 list_t *q = p; 36 p = p->next; 37 38 void *obj = list_l2d(q); 39 printf("free (node) %p next (list) %p\n", obj, p); 40 free(obj); 41 } 42 } 43 44 static void 45 foo_show(list_t *head) 46 { 47 for (list_t *p = head; p != NULL; p = p->next) { 48 foo_t *obj = list_l2d(p); 49 50 printf("show (list) %p next (list) %p \t: " 51 "show (node) %p = {0x%x, {%p, %d}}\n", 52 &obj->link, obj->link.next, 53 obj, obj->data, obj->link.next, obj->link.offset); 54 } 55 } 56 57 int 58 main(int argc, char *argv[]) 59 { 60 if (argc != 2) { 61 fprintf(stderr, "Usage: %s <num>\n", argv[0]); 62 return -1; 63 } 64 65 list_t *head = NULL; 66 for (int i = 0; i < atoi(argv[1]); i++) { 67 foo_t *p = (foo_t *)malloc(sizeof (foo_t)); 68 if (p == NULL) /* error */ 69 return -1; 70 p->data = 0x1001 + i; 71 72 foo_init(&head, (void *)p, offsetof(foo_t, link)); 73 } 74 75 foo_show(head); 76 foo_fini(head); 77 78 return 0; 79 }
注意: list_l2d()与container_of()是等效的。 例如:
48 foo_t *obj = list_l2d(p);
等效于
foo_t *obj = container_of(p, foo_t, link);
4. Makefile
1 CC = gcc 2 CFLAGS = -g -Wall -m32 -std=gnu99 3 4 all: foo 5 6 foo: foo.o list.o 7 ${CC} ${CFLAGS} -o $@ $^ 8 9 foo.o: foo.c 10 ${CC} ${CFLAGS} -c $< 11 12 list.o: list.c list.h 13 ${CC} ${CFLAGS} -c $< 14 15 clean: 16 rm -f *.o 17 18 clobber: clean 19 rm -f foo 20 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) 0x81a8008 init (node) 0x81a8018 init (node) 0x81a8028 show (list) 0x81a800c next (list) 0x81a801c : show (node) 0x81a8008 = {0x1001, {0x81a801c, 4}} show (list) 0x81a801c next (list) 0x81a802c : show (node) 0x81a8018 = {0x1002, {0x81a802c, 4}} show (list) 0x81a802c next (list) (nil) : show (node) 0x81a8028 = {0x1003, {(nil), 4}} free (node) 0x81a8008 next (list) 0x81a801c free (node) 0x81a8018 next (list) 0x81a802c free (node) 0x81a8028 next (list) (nil)
6. 用gdb查看链表
(gdb) b foo_show Breakpoint 1 at 0x80485d4: file foo.c, line 47. (gdb) r 3 Starting program: /tmp/list/foo 3 init (node) 0x804b008 init (node) 0x804b018 init (node) 0x804b028 Breakpoint 1, foo_show (head=0x804b00c) at foo.c:47 47 for (foo_t *p = list_head(head); p != NULL; p = list_next(&p->link)) { (gdb) # (gdb) x /2x head 0x804b00c: 0x0804b01c 0x00000004 (gdb) x /2x head->next 0x804b01c: 0x0804b02c 0x00000004 (gdb) x /2x head->next->next 0x804b02c: 0x00000000 0x00000004 (gdb) # (gdb) p head $1 = (list_t *) 0x804b00c (gdb) # (gdb) x /3x 0x804b00c-0x4 0x804b008: 0x00001001 0x0804b01c 0x00000004 (gdb) x /3x 0x804b01c-0x4 0x804b018: 0x00001002 0x0804b02c 0x00000004 (gdb) x /3x 0x804b02c-0x4 0x804b028: 0x00001003 0x00000000 0x00000004 (gdb) #
小结:
在这一版本的侵入式链表实现中,实现细节已经被充分屏蔽,核心函数就两个,list_d2l()和list_l2d(),也很容易理解。当然,对于消费者程序来说,无需知晓数据(data)结点的内存首地址与链表(list)结点的内存首地址之间是如何相互转换的。
后记:
在上面的代码实现中,list_d2l()总是会将((list_t *)p)->next设置成NULL。这么做存在着一个问题,那就是一旦链表构造完成后,如果想从某一个数据结点通过list_d2l()找到对应的链表结点,就会将链表截断。于是,我对list_d2l()做了一点修改,并增加了三个基本的链表操作函数list_insert_tail(), list_insert_head()和list_delete()。
1. list_d2l()和list_l2d()
1 /* 2 * Cast ptr of DATA object node to ptr of LIST node 3 */ 4 list_t * 5 list_d2l(void *object, size_t offset) 6 { 7 if (object == NULL) 8 return NULL; 9 10 return (list_t *)((char *)object + offset); 11 } 12 13 /* 14 * Cast ptr of LIST node to ptr of DATA object node 15 */ 16 void * 17 list_l2d(list_t *list) 18 { 19 if (list == NULL) 20 return NULL; 21 22 return (void *)((char *)list - list->offset); 23 }
2. LIST_INIT_NODE()
1 #define LIST_INIT_NODE(list, offset) do { \ 2 (list)->next = NULL; \ 3 (list)->offset = (offset); \ 4 } while (0)
3. 将数据结点插入到侵入式链表中
- list_insert_tail() // 尾插法
- list_insert_head() // 头插法
1 /* 2 * Insert an object after the tail of list 3 */ 4 void 5 list_insert_tail(list_t **head, void *object, size_t offset) 6 { 7 if (object == NULL) 8 return; 9 10 list_t *node = list_d2l(object, offset); 11 LIST_INIT_NODE(node, offset); 12 13 if (*head == NULL) { 14 *head = node; 15 return; 16 } 17 18 list_t *tail = NULL; 19 for (list_t *p = *head; p != NULL; p = p->next) 20 tail = p; 21 tail->next = node; 22 } 23 24 /* 25 * Insert an object before the head of list 26 */ 27 void 28 list_insert_head(list_t **head, void *object, size_t offset) 29 { 30 if (object == NULL) 31 return; 32 33 list_t *node = list_d2l(object, offset); 34 LIST_INIT_NODE(node, offset); 35 36 if (*head == NULL) { 37 *head = node; 38 return; 39 } 40 41 node->next = *head; 42 *head = node; 43 }
4. 从侵入式连表中删除一个结点
1 /* 2 * Delete a node from list 3 */ 4 void 5 list_delete(list_t **head, list_t *node) 6 { 7 if (head == NULL || *head == NULL || node == NULL) 8 return; 9 10 if (*head == node) { 11 *head = node->next; 12 return; 13 } 14 15 list_t *q = *head; 16 for (list_t *p = *head; p != NULL; p = p->next) { 17 if (p == node) 18 break; 19 q = p; 20 } 21 q->next = node->next; 22 }
如对完整的代码实现感兴趣,请戳这里。