前一节介绍的侵入式链表实现在封装性方面做得不好,因为会让消费者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

 #ifndef _LIST_H
#define _LIST_H #ifdef __cplusplus
extern "C" {
#endif /**
* offsetof - offset of a structure member
* @TYPE: the type of the struct.
* @MEMBER: the name of the member within the struct.
*/
#define offsetof(TYPE, MEMBER) ((size_t)(&(((TYPE *)0)->MEMBER))) typedef struct list_s {
struct list_s *next;
size_t offset;
} list_t; extern list_t *list_d2l(void *object, size_t offset);
extern void *list_l2d(list_t *list); #ifdef __cplusplus
}
#endif #endif /* _LIST_H */
  • list_d2l(): 根据数据(data)结点的内存首地址得到侵入式链表(list)结点的内存首地址。
  • list_l2d(): 根据侵入式链表(list)结点的内存首地址得到数据(data)结点的内存首地址。

2. list.c

 /*
* Generic single linked list implementation
*/
#include <stdio.h>
#include "list.h" list_t *
list_d2l(void *object, size_t offset)
{
if (object == NULL)
return NULL; list_t *p = (list_t *)((char *)object + offset);
p->offset = offset;
p->next = NULL; return p;
} void *
list_l2d(list_t *list)
{
if (list == NULL)
return NULL; return (void *)((char *)list - list->offset);
}
  • list_d2l(): 侵入式链表结点的内存首地址 = 数据结点的内存首地址 + offset
  • list_l2d(): 数据结点的内存首地址 = 侵入式链表结点的内存首地址 - offset

3. foo.c

 #include <stdio.h>
#include <stdlib.h>
#include "list.h" typedef struct foo_s {
int data;
list_t link;
} foo_t; static void
foo_init(list_t **head, void *object, size_t offset)
{
if (object == NULL)
return; printf("init (node) %p\n", object);
list_t *node = list_d2l(object, offset); if (*head == NULL) {
*head = node;
return;
} list_t *tail = NULL;
for (list_t *p = *head; p != NULL; p = p->next)
tail = p;
tail->next = node;
} static void
foo_fini(list_t *head)
{
list_t *p = head;
while (p != NULL) {
list_t *q = p;
p = p->next; void *obj = list_l2d(q);
printf("free (node) %p next (list) %p\n", obj, p);
free(obj);
}
} static void
foo_show(list_t *head)
{
for (list_t *p = head; p != NULL; p = p->next) {
foo_t *obj = list_l2d(p); printf("show (list) %p next (list) %p \t: "
"show (node) %p = {0x%x, {%p, %d}}\n",
&obj->link, obj->link.next,
obj, obj->data, obj->link.next, obj->link.offset);
}
} int
main(int argc, char *argv[])
{
if (argc != ) {
fprintf(stderr, "Usage: %s <num>\n", argv[]);
return -;
} list_t *head = NULL;
for (int i = ; i < atoi(argv[]); i++) {
foo_t *p = (foo_t *)malloc(sizeof (foo_t));
if (p == NULL) /* error */
return -;
p->data = 0x1001 + i; foo_init(&head, (void *)p, offsetof(foo_t, link));
} foo_show(head);
foo_fini(head); return ;
}

注意: list_l2d()与container_of()是等效的。 例如:

                 foo_t *obj = list_l2d(p);

等效于

                   foo_t *obj = container_of(p, foo_t, link);

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
init (node) 0x81a8008
init (node) 0x81a8018
init (node) 0x81a8028
show (list) 0x81a800c next (list) 0x81a801c : show (node) 0x81a8008 = {0x1001, {0x81a801c, }}
show (list) 0x81a801c next (list) 0x81a802c : show (node) 0x81a8018 = {0x1002, {0x81a802c, }}
show (list) 0x81a802c next (list) (nil) : show (node) 0x81a8028 = {0x1003, {(nil), }}
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 at 0x80485d4: file foo.c, line .
(gdb) r
Starting program: /tmp/list/foo
init (node) 0x804b008
init (node) 0x804b018
init (node) 0x804b028 Breakpoint , foo_show (head=0x804b00c) at foo.c:
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
$ = (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()

 /*
* Cast ptr of DATA object node to ptr of LIST node
*/
list_t *
list_d2l(void *object, size_t offset)
{
if (object == NULL)
return NULL; return (list_t *)((char *)object + offset);
} /*
* Cast ptr of LIST node to ptr of DATA object node
*/
void *
list_l2d(list_t *list)
{
if (list == NULL)
return NULL; return (void *)((char *)list - list->offset);
}

2. LIST_INIT_NODE()

 #define LIST_INIT_NODE(list, offset) do {       \
(list)->next = NULL; \
(list)->offset = (offset); \
} while ()

3. 将数据结点插入到侵入式链表中

  • list_insert_tail()    // 尾插法
  • list_insert_head()  // 头插法
 /*
* Insert an object after the tail of list
*/
void
list_insert_tail(list_t **head, void *object, size_t offset)
{
if (object == NULL)
return; list_t *node = list_d2l(object, offset);
LIST_INIT_NODE(node, offset); if (*head == NULL) {
*head = node;
return;
} list_t *tail = NULL;
for (list_t *p = *head; p != NULL; p = p->next)
tail = p;
tail->next = node;
} /*
* Insert an object before the head of list
*/
void
list_insert_head(list_t **head, void *object, size_t offset)
{
if (object == NULL)
return; list_t *node = list_d2l(object, offset);
LIST_INIT_NODE(node, offset); if (*head == NULL) {
*head = node;
return;
} node->next = *head;
*head = node;
}

4. 从侵入式连表中删除一个结点

 /*
* Delete a node from list
*/
void
list_delete(list_t **head, list_t *node)
{
if (head == NULL || *head == NULL || node == NULL)
return; if (*head == node) {
*head = node->next;
return;
} list_t *q = *head;
for (list_t *p = *head; p != NULL; p = p->next) {
if (p == node)
break;
q = p;
}
q->next = node->next;
}

如对完整的代码实现感兴趣,请戳这里

侵入式单链表的简单实现(cont)的更多相关文章

  1. C:单链表的简单实现

    前言 今天整理资料的时候翻出来的文件,发现是以前学习数据结构的时候写的代码,当初是看郝凯老师的视频学习的C语言的数据结构,下面是对于一个单链表的简单的实现. /** ***************** ...

  2. 用最简单的方式学Python单链表

    Python 实现单链表 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列和链表都能够对其中的元素保持一定得顺序,但采用的方式 ...

  3. C++侵入式链表

    C++标准模板库中的list是非侵入式的链表,当我们通过对象来删除容器中的对象时,需要从头到尾查找一次得到iterator,最后通过iterator来删除对象.这样删除容器中的对象时比较缓慢,所以就实 ...

  4. 单链表数据结构 - java简单实现

    链表中最简单的一种是单向链表,每个元素包含两个域,值域和指针域,我们把这样的元素称之为节点.每个节点的指针域内有一个指针,指向下一个节点,而最后一个节点则指向一个空值.如图就是一个单向链表 一个单向链 ...

  5. 用最容易的方式学会单链表(Python实现)

    单链表与数组 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列也会有如下缺点: 一个动态数组的长度可能超过实际存储数组元素所需 ...

  6. C语言写单链表的创建、释放、追加(即总是在最后的位置增加节点)

    昨天周末给学妹讲了一些指针的知识,本来我对指针就是似懂非懂的状态,经过昨天一讲,我对指针的学习就更深刻了 果然给别人讲课也是学习的一个方法.加上最近复习数据结构,发现我的博客里没有链表的博文,所以趁这 ...

  7. 数据结构(一) 单链表的实现-JAVA

    数据结构还是很重要的,就算不是那种很牛逼的,但起码得知道基础的东西,这一系列就算是复习一下以前学过的数据结构和填补自己在这一块的知识的空缺.加油.珍惜校园中自由学习的时光.按照链表.栈.队列.排序.数 ...

  8. java 单链表 练习

    练习一下java单链表的简单习题 package com.test1; import java.util.Stack; public class SingleListDemo { /** * 返回单链 ...

  9. C++实现简单的单链表

    下面实现的是一个简单的单链表 功能不多,学习使用 #pragma once #include <iostream> using namespace std; class ListEx { ...

随机推荐

  1. onenote无法更新,提示无法流式传输、无法登陆等问题解答

    onenote无法更新,提示无法流式传输 修改DNS 4.2.2.1 和 4.2.2.2 onenote反复提示登录 升级到IE11

  2. python将json转csv

    现有一个需求要将json转成excel,使用python将其转为csv格式,使用excel打开即可. import json import csv import codecs f = open('te ...

  3. PyQt4 QListWidget 使用教程

    转自:http://blog.csdn.net/seeground/article/details/49177387?locationNum=3&fps=1 listWidget = QLis ...

  4. 在ASP.NET根据DataTable中的内容导出Excel

    前台代码: <asp:Button ID="btnExcel" runat="server" Text="Excel导出" CssCl ...

  5. windows 10 RelativePanel

    The new RelativePanel implements a style of layout that is defined by the relationships between its ...

  6. kolla-ansible 重新部署 ceph 遇到的问题

    问题 TASK [ceph : Fetching Ceph keyrings] ******************************************* fatal: [controll ...

  7. wpa2破解代码思路(教你写poc)

    前言:此篇关于1.wpa2协议漏洞的产生原因:2.scapy这个数据包库的基础:3.最后讲解代码思路,让你们从伪代码可以直接写成代码展开介绍分享~ 本文作者:i春秋签约作家——kaikaix 1.大家 ...

  8. 钉钉机器人集成Jenkins推送消息模板自定义发送报告

    一.由于公司同样也使用了钉钉.那么在做Jenkins集成自动化部署的时候,也是可以集成钉钉的. 那种Jenkins下载钉钉插件集成,简单设置就可以完成了.我们今天要做的是,定制化的发送消息. 钉钉推送 ...

  9. C#-语言基础+数据类型+运算符

    一.C#语言基础 新建项目:文件→新建→项目→Visual C#(默认.NET Framework 4.5)→控制台应用程序 1.项目结构 (1)项目后缀 .config ——配置文件(存放配置参数文 ...

  10. Optional类

    参照: 一篇简单使用介绍 官网详细用法介绍 包含各种例子的cheetsheet 一个封装某个value的容器 一般可用于方法返回值类型,提醒调用方,这个值可能为null,所以需要处理(因为空指针异常是 ...