前一节介绍的侵入式链表实现在封装性方面做得不好,因为会让消费者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. Android-Java读写文件到自身APP目录

    界面: Layout: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: ...

  2. 如何彻底删除TFS的工作项字段

    TFS的工作项字段可以在所有工作项类型之间共享.例如自定义了一个字段"验证迭代"(Mycompany.IterationValidation)那么在需求.Bug中都可以添加这个字段 ...

  3. WPF自定义滚动条

    我修改了一些地方,部分代码参考了博主 https://www.cnblogs.com/xiaomingg/ <!-- ScrollViewer 滚动条 --> <Style x:Ke ...

  4. WP8.1StoreApp(WP8.1RT)---MessageBox与MessageDialog

    在WP7和WP8中,MessageBox是跟WinForm中一样常用的对话框,但是有一个显著的缺点,就是WP7/8中默认的MessageBox是阻塞线程的.也许是由于这个原因,WP8.1/Win8中采 ...

  5. AutoMapper之如何开始,适合入门和演示

    原来想应该介绍下背景说明下好处什么的,仔细想都是废话 ,直接上代码吧. 首先有两个类,一个是和数据库对应的实体 Student,一个是和页面展示相关的页面模型 StudentModel. /// &l ...

  6. IIS隐藏网站

    IIS隐藏网站 1.站点建立一个文件夹:Test 2.在F盘新建Web文件夹(放要隐藏的网站) 3.右键Test文件夹-新建虚拟目录,虚拟目录指向步骤2 4.删除Test文件夹即可

  7. 64位虚拟机中安装CentOS_6.7

    虚拟机VirtualBox-4.3.24-98716-Win.1425444683.exe,操作系统选用CentOS-6.7-x86_64-LiveDVD .iso. 1) 启动VirtualBox, ...

  8. 自定义JSON返回字段

    今天看到一篇文章,里面介绍了如何自定义返回json字段,感觉挺好用的,这里学习一下. 实现工具类: /** * @author fengzp * @date 17/2/20上午10:34 * @ema ...

  9. [JS] 瀑布流加载

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  10. 如何使用Node爬虫利器Puppteer进行自动化测试

    文:华为云DevCloud 乐少 1.背景 1.1 前端自动化测试较少 前端浏览器众多导致页面兼容性问题比较多,另外界面变化比较快,一个月内可能页面改版两三次,这样导致对前端自动化测试较少,大家也不是 ...