侵入式单链表的简单实现(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 { ...
随机推荐
- 20145233计算机病毒实践九之IDA的使用
20145233计算机病毒实践之IDA的使用 PSLIST导出函数做了什么 这个函数是一个export函数,所以在view中选择export 查到后,双击打开这个函数的位置 仔细看这个函数可以发现这个 ...
- task4:结对项目-词频统计
结对人:周楠 思路:利用TreeMap实现key字典序,然后输出到LinkedList,然后用Comparator,实现字典值从大到小排序,但是key实现值相同的key字典序的想出的实现方法,但是一直 ...
- solr特点七:Plugins(扩展点)
http://wiki.apache.org/solr/SolrPlugins 在 Solr 1.3 中,扩展 Solr 以及配置和重新整理扩展变得十分简单.以前,您需要编写一个 SolrReques ...
- 2-初步了解C#-类与对象
本篇博客对应视频讲解 回顾 我们在第一篇文章中讲了编程中最基本的内容,如输入输出.字符串处理.数字类型计算.分支及循环结构等.无论学习什么语言,这些内容都是相通的. 本篇博客主要演示列表(List)的 ...
- django系列3.3--view视图函数, render, reverse(未完待续)
1.view视图函数 urls分发之后所用到的处理函数 2.render 用于渲染页面 在views.py中常用 from django.shortcuts import render, HttpRe ...
- GO学习笔记 - 函数名前面是否有输入参数肯定是不一样的!!
在刚接触GO语言时候,我相信你也会有这种困惑,为什么有的函数名前面有输入参数,而一些却没有,它们是否有差别?确实有差别,没有输入参数,是一般的函数:有输入参数,是结构的方法,输入参数叫做“方法接收者” ...
- UnSafe类中的一些重要方法
UnSafe类中的一些重要方法 JDK中的rt.jar保重Unsafe类中提供了硬件级别的原子性操作,Unsafe类中的方法都是navtice方法,他们使用JNI的方式访问C++实现库,下面我们来了解 ...
- “全栈2019”Java第一百章:局部内部类可以实现接口吗?
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- IDEA中配置SpringMVC框架 第一个演示【转】
环境: intellij IDEA 2017 CI JDK 1.8 tomcat 8.5.23 具体步骤 1.新建项目 勾选Spring MVC .Web Application(勾选了Spring ...
- SQL Server IF Exists 判断数据库对象是否存在的用法
1 判断数据库是否存在Sql代码 if exists (select * from sys.databases where name = ’数据库名’) drop database [数据库名] ...