侵入式单链表的简单实现

众所周知,一个普通的单链表看起来是这样子滴,

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离真正的侵入式单链表实现还是有距离滴。欲知后事如何,且听下回分解。

posted @ 2017-05-04 17:09  veli  阅读(4031)  评论(0编辑  收藏  举报