前一节介绍的侵入式链表实现在封装性方面做得不好,因为会让消费者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. 关于如何参与到开源项目中《How To Succeed In Open Source ( In Ways You Haven't Considered Yet )》

    转自:http://gaslight.co/blog/how-to-succeed-in-open-source-in-ways-you-havent-considered-yet It’s Easy ...

  2. vmware之VMware Remote Console (VMRC) SDK(三)

    前两节我们介绍了vmrc sdk的基本用法.在前面的demo中,有一个关键的问题是,我们现在所作的工作都是基于局域网的,作为应用层面上,主机不会直接暴露给用户,而是通过一系列的web service服 ...

  3. java环境和Tomcat环境

    这些变量名是一样的,变量的值需要自己根据自己的安装位置来确定 JAVA_HOME C:\Program Files\Java\jdk1.8.0_151 CATALINA_HOME(这个可能不需要) D ...

  4. 虚拟化 - VMware

    和VirtualBox一样,也需要关掉Hyper-V才能启动虚拟机,否则会报Guard的错误. 网络 [转]VMware网络连接模式-桥接.NAT以及仅主机模式的详细介绍和区别 桥接 就好像在局域网中 ...

  5. VisualStudio2017 远程 调试 IIS 服务器 web网站

    小伙伴们,本次测试好好的程序发布到服务器挂到IIS后我勒个*,,, 神马情况,为啥和我本地运行结果不一致,Fuc*... 没遇到的小伙伴估计也看不到此篇文章了吧,Log日志调试,嗯 不错,good i ...

  6. Delphi XE7中使用JSON

    Delphi XE7有一个对JSON处理的单元,在你需要使用JSON的单元里面引入"System.json",随后你就可以用Delphi自己的json处理类了.我写的小例子只是对包 ...

  7. [Objective-C语言教程]继承(25)

    面向对象编程中最重要的概念之一是继承.继承允许根据一个类定义另一个类,这样可以更容易地创建和维护一个应用程序. 这也提供了重用代码功能和快速实现时间的机会. 在创建类时,程序员可以指定新类应该继承现有 ...

  8. [实战] 给现有的NGINX二进制RPM包加新模块

    [实战] 给现有的NGINX二进制RPM包加新模块 一.前言 在做 wiki 的镜像,这样以后文章就可以使用外链了(链接直接跳转墙内小站). 遇到的问题就是:我的 NGINX 包安装的时候图方便采用 ...

  9. Settings app简单学习记录

    Settings是android系统设置的入口.主界面由Settings.java以及settings_headers.xml构成. Settings类继承自PreferenceActivity,而P ...

  10. UITableView 头部效果/放大/移动跟随效果

    [self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOp ...