侵入式单链表的简单实现(cont)
前一节介绍的侵入式链表实现在封装性方面做得不好,因为会让消费者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)的更多相关文章
- C:单链表的简单实现
前言 今天整理资料的时候翻出来的文件,发现是以前学习数据结构的时候写的代码,当初是看郝凯老师的视频学习的C语言的数据结构,下面是对于一个单链表的简单的实现. /** ***************** ...
- 用最简单的方式学Python单链表
Python 实现单链表 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列和链表都能够对其中的元素保持一定得顺序,但采用的方式 ...
- C++侵入式链表
C++标准模板库中的list是非侵入式的链表,当我们通过对象来删除容器中的对象时,需要从头到尾查找一次得到iterator,最后通过iterator来删除对象.这样删除容器中的对象时比较缓慢,所以就实 ...
- 单链表数据结构 - java简单实现
链表中最简单的一种是单向链表,每个元素包含两个域,值域和指针域,我们把这样的元素称之为节点.每个节点的指针域内有一个指针,指向下一个节点,而最后一个节点则指向一个空值.如图就是一个单向链表 一个单向链 ...
- 用最容易的方式学会单链表(Python实现)
单链表与数组 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列也会有如下缺点: 一个动态数组的长度可能超过实际存储数组元素所需 ...
- C语言写单链表的创建、释放、追加(即总是在最后的位置增加节点)
昨天周末给学妹讲了一些指针的知识,本来我对指针就是似懂非懂的状态,经过昨天一讲,我对指针的学习就更深刻了 果然给别人讲课也是学习的一个方法.加上最近复习数据结构,发现我的博客里没有链表的博文,所以趁这 ...
- 数据结构(一) 单链表的实现-JAVA
数据结构还是很重要的,就算不是那种很牛逼的,但起码得知道基础的东西,这一系列就算是复习一下以前学过的数据结构和填补自己在这一块的知识的空缺.加油.珍惜校园中自由学习的时光.按照链表.栈.队列.排序.数 ...
- java 单链表 练习
练习一下java单链表的简单习题 package com.test1; import java.util.Stack; public class SingleListDemo { /** * 返回单链 ...
- C++实现简单的单链表
下面实现的是一个简单的单链表 功能不多,学习使用 #pragma once #include <iostream> using namespace std; class ListEx { ...
随机推荐
- gvim 全屏 插件
1.这里下载插件zip解压后,将fullscreen.dll放到gvim.exe同目录下 2.执行 :call libcallnr()//切换全屏模式 3.上面的命令非常麻烦,可以在_vimrc中ma ...
- c++中的隐藏及重载、重写与隐藏的区别
c/c++中的隐藏 举个栗子 class A { public : void fun1(int a, int b) { cout<<"abcd"<<end ...
- Buffer Pool--内存相关术语
虚拟地址空间(virtual address space): 供应用程序能够申请访问的最大地址空间,32位系统上为4GB,64位系统上是8TB,虚拟地址空间映射的数据不一定存放在物理内存中,还可能存放 ...
- solr特点三: 排序样例汇总
目的是提供solrj 实现 查询的样例参考 单维度排序 //查询条件 query.setQuery(queryString); // add 是添加 query.addSortField(field_ ...
- WP8.1StoreApp(WP8.1RT)---添加推送功能和获取系统信息
添加推送通知 1:Package.appxmanifest中的声明添加后台任务的推送通知权限 2:var channel = await PushNotificationChannelManager. ...
- MVC页面移除HTTP Header中服务器信息
默认情况下,每一个MVC请求的HTTP Header中都会包含着当前服务器的一些信息,出于安全还是性能还是处女座的强迫症等等,都想把这些信息移除掉,增加一些应用程序的神秘感,如下,默认情况下Chrom ...
- Cookie背景了解
Cookie的复数形态是Cookies, 英文的意思是小甜饼,小饼干. 类型为小型文本文件, 指某些网站为了辨别用户身份储存在用户本地中断上的数据. 是前网景公司的员工 卢-蒙特利在1993年3月发明 ...
- Delphi XE7的安卓程序如何调用JAVA的JAR,使用JAVA的类?
本文使用工具和全部源码下载: http://download.csdn.net/detail/sunylat/8190765 为什么我们要在Delphi XE7的安卓程序调用JAVA的JAR,使用JA ...
- Hibernate 干货
一 .hibernate概念: hibernate应用在javaee 三层框架中的dao层(web 层 --service层--dao层),在dao层实现对数据库的CRUD操作.hibernate是对 ...
- Java_IO流输入输出
第三章 输入输出 一.I/O Input/Output 二.File 用途:对文件和目录进行常规操作(除文件读写操作外). 方法:exists():判断文件或目录是否存在 isFile():判断是否是 ...