09 简化Linux链表list.h

09 简化Linux链表list.h

作者将狼才鲸
创建日期2023-03-08

  • Demo运行结果:
jim@DESKTOP-SVP3BEM MINGW64 /d/1_git/many-repositories/25_双向链表 (develop)
$ make
gcc -o demo main.c

jim@DESKTOP-SVP3BEM MINGW64 /d/1_git/many-repositories/25_双向链表 (develop)
$ make run
./demo

put:
[0] HELLO WORLD!
[1] hello world!
[2] this is a test
[3] string 4
[4] my name is
[5] demo

get:
[1] demo
[2] my name is
[3] string 4
[4] this is a test
[5] hello world!
[6] HELLO WORLD!

jim@DESKTOP-SVP3BEM MINGW64 /d/1_git/many-repositories/25_双向链表 (develop)

  • 源码:
  1. Makefile
default:
	gcc -o demo main.c
run:
	./demo
clean:
	rm demo

  1. main.c
#include "list.h"
#include <string.h>	/* strcpy */
#include <stdio.h>	/* printf */

#define USER_DATA_SIZE	32

#define USER_TEST_COUNT	6
#define USER_STRING1	"HELLO WORLD!"
#define USER_STRING2	"hello world!"
#define USER_STRING3	"this is a test"
#define USER_STRING4	"string 4"
#define USER_STRING5	"my name is"
#define USER_STRING6	"demo"

typedef struct userlist {
	struct list_head node;		/* 每个数据都绑定一个链表节点 */
	char data[USER_DATA_SIZE];	/* 携带的数据 */
} userlist_t;

static char *str[USER_TEST_COUNT] = {USER_STRING1, USER_STRING2,
									 USER_STRING3, USER_STRING4,
									 USER_STRING5, USER_STRING6};
static userlist_t userdata[USER_TEST_COUNT];

int main()
{
	struct list_head free, used;	/* 链表入口 */
	struct list_head *pos, *n;		/* 临时变量 */
	int i;
	
	init_list_head(&free);
	/* 初始化,将所有数据放到未使用队列(如果使用malloc则可省去此步骤) */
	for (i = 0; i < USER_TEST_COUNT; i++)
		list_add(&userdata[i].node, &free);

	init_list_head(&used);
	/* 获取所有数据空间并赋值,将已赋值的空间放入已使用队列 */
	i = 0;
	printf("\nput:\n");
	list_for_each_safe(pos, n, &free) {
		list_del(pos);
		userlist_t *datanode = list_entry(pos, userlist_t, node);
		printf("[%d] %s\n", i, str[i]);
		strcpy(datanode->data, str[i++]);
		list_add(&datanode->node, &used);
	}

	/* 获取并打印所有数据,将已使用完的空间重新放入空闲队列 */
	i = 0;
	printf("\nget:\n");
	list_for_each_safe(pos, n, &used) {
		list_del(pos);
		userlist_t *datanode = list_entry(pos, userlist_t, node);
		printf("[%d] %s\n", ++i, datanode->data);
		list_add(&datanode->node, &free);
	}

	return 0;
}
  1. list.h
/******************************************************************************
 * \brief	双向链表(不直接包含数据)
 * \details	约定链表入口地址本身是尾(最后弹出)地址,head->next是头(最先弹出)地址;
 *			链表入口地址本身是无效的节点,即使弹出所有的节点,也只弹到入口之前
 *			的节点为止;
 *			一个双向链表实际上就是一个最简单的FIFO,就一个先进先出功能
 * \note	File format: UTF-8,中文编码:UTF-8;
 *			本链表不包含具体的数据,数据须在包含本链表成员的上层结构体中进行操作,
 *			也就是说本链表不能单独使用,必须和上层模块联用;
 *			本模块当前必须在gcc中才能编译通过,在msvc中不行,要想在Windows中使用
 *			需要去除typeof关键字,并在参数中增加一个变量;
 * \remarks	基于linux_6.1-rc4\scripts\kconfig\list.h,
 *			该源文件是从include\linux\list.h简化而来;
 *			Linux kernel源码中其它可供参考的链表还有:
 *			linux_6.1-rc4\scripts\kconfig\list.h  最好用
 *			linux_6.1-rc4\tools\usb\usbip\libsrc\list.h  可用
 *			linux_6.1-rc4\scripts\mod\list.h  可用
 *			linux_6.1-rc4\tools\firewire\list.h  另一种写法
 *			linux_6.1-rc4\tools\include\linux\list.h  太全
 *			linux_6.1-rc4\include\linux\list.h  最全
 * \author	中文注释:将狼才鲸
 * \date	2023-03-05
 ******************************************************************************/

#ifndef LIST_H
#define LIST_H

#include <stddef.h>	/* offsetof size_t */

/**
 * \brief	双向链表结构体
 */
struct list_head {
	struct list_head *next, *prev;
};

/**
 * \brief	定义一个链表节点并赋初值,只是简化写法
 * \details	这个接口一般用不到,实际使用时会用init_list_head()
 * \param	name:	要定义的链表节点名
 */
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name)	\
	struct list_head name = LIST_HEAD_INIT(name)

/** 为链表的实际应用留下扩展接口 */

/**
 * \brief	获取一个结构体中的某个成员相对于结构体首地址的偏移量
 * \details	用于操作链表上层的带有有效数据+链表成员的结构体
 * \remarks	在stddef.h中已有该宏定义函数
 * \param	TYPE:	上层结构体名称
 * \param	MEMBER:	结构体中要查找偏移量的成员,一般这个成员是链表结构体指针
 * \return	结构体成员相对于结构体首地址的偏移量
 */
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/**
 * \brief	从结构体中某一成员地址逆推出该结构体的首地址
 * \details	用于操作链表上层的带有有效数据+链表成员的结构体
 * \note	typeof是Linux GNU C(GCC)中扩展的关键字,从定义的变量名逆推出该变量的
 *			类型,如int a; typeof(a) b;中的第二句与int b;的效果相同;
 *			在Windows等其它编译器中编译会不通过;
 *			原始定义在include/linux/kernel.h中
 * \param	ptr:	需要逆推的上层结构体中的某个成员地址,一般是链表成员的地址
 * \param	type:	上层结构体的类型名,一般该类型是结构体
 * \param	member:	上层结构体中成员地址的名称,也就是a.b或a->b里面的这个b
 */
#define container_of(ptr, type, member) ({					\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

/**
 * \brief	获取已嵌入链表结构体的上层结构体地址
 * \param	ptr:	上层结构体中的&struct list_head指针
 * \param	type:	嵌入了list_head成员的上层结构体类型名
 * \param	member:	上层结构体中list_head所属的名称
 */
#define list_entry(ptr, type, member)	\
	container_of(ptr, type, member)

/**
 * \brief	循环整个链表时的for(;x;x)语句(循环时不能删除当前链表节点)
 * \param	pos:	当前循环到的节点,是个临时变量
 * \param	head:	链表入口
 */
#define list_for_each(pos, head)	\
	for (pos = (head)->next; pos != (head); pos = pos->next)

/**
 * \brief	循环整个链表时的for(;x;x)语句(循环时可以删除当前链表节点)
 * \param	pos:	当前循环到的节点,是个临时变量
 * \param	n:		作为临时变量的节点
 * \param	head:	链表入口
 */
#define list_for_each_safe(pos, n, head)					\
	for (pos = (head)->next, n = pos->next; pos != (head);	\
		pos = n, n = pos->next)

/**
 * \brief	在上层结构体的基础上循环整个链表时的for(;x;x)语句(循环时不能删除当前链表节点)
 * \details	虽然是在上层带有效数据的结构体指针上进行循环,但是实际实现时
 *			是以其中的链表结构体作为依据
 * \param	pos:	要循环的上层结构体临时变量,该结构体中带有链表成员,
 *					pos值用作变量,本身不必预先赋值,但是循环时不能删除它
 * \param	head:	上层结构体中的list_head成员的地址
 * \param	member:	上层结构体中的list_head成员的名称
 * \note	head虽然是链表入口地址,但本身是尾(最后弹出)地址,head->next是头(最先弹出)地址;
 */
#define list_for_each_entry(pos, head, member)					\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     &pos->member != (head);								\
	     pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * \brief	在上层结构体的基础上循环整个链表时的for(;x;x)语句(支持循环时删除当前链表节点)
 * \details	虽然是在上层带有效数据的结构体指针上进行循环,但是实际实现时
 *			是以其中的链表结构体作为依据
 * \param	pos:	要循环的上层结构体临时变量,该结构体中带有链表成员,
 *					pos值用作变量,本身不必预先赋值,循环时可以删除它
 * \param	n:		上层结构体临时变量
 * \param	head:	上层结构体中的list_head成员的地址
 * \param	member:	上层结构体中的list_head成员的名称
 * \note	head虽然是链表入口地址,但本身是尾(最后弹出)地址,head->next是头(最先弹出)地址;
 */
#define list_for_each_entry_safe(pos, n, head, member)			\
	for (pos = list_entry((head)->next, typeof(*pos), member),	\
		 n = list_entry(pos->member.next, typeof(*pos), member);\
	     &pos->member != (head);								\
	     pos = n, n = list_entry(n->member.next, typeof(*n), member))

/* 因为下面都是内联函数,所以无需放在.c中,放在.h中即可,编译时不会
   重复编译,而是会像宏定义一样内联展开*/

/** 私有函数 */

/**
 * \brief	在链表序列中插入一个链表节点(已知要插入位置的之前和之后的节点)
 * \param	_new:	要插入的链表节点
 * \param	prev:	插入点前方的链表节点
 * \param	next:	插入点后方的链表节点
 */
static inline void __list_add(struct list_head *_new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = _new;
	_new->next = next;
	_new->prev = prev;
	prev->next = _new;
}

/**
 * \brief	在链表序列中删除一个链表节点(已知要删除位置的之前和之后的节点)
 * \param	prev:	删除点前方的链表节点
 * \param	next:	删除点后方的链表节点
 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
	next->prev = prev;
	prev->next = next;
}

/* 接口函数 */

/**
 * \brief	初始化一个链表节点
 * \param	list:	要初始化的链表指针
 */
static inline void init_list_head(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

/**
 * \brief	判断链表是否为空
 * \param	head:	要判断的链表指针
 */
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

/**
 * \brief	将一个链表节点插入到一条链表中
 * \param	new:	要插入的链表节点
 * \param	head:	要加入的那条链表的链表入口
 *					(链表入口所属的那个节点是链表尾,head->next是链表头)
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

/**
 * \brief	将一个链表节点插入到链表尾,也就是更新了这条链表的入口节点(入口并不是链表头)
 * \param	_new:	要插入的链表节点
 * \param	head:	要加入的那条链表的链表入口
 *					(链表入口所属的那个节点是链表尾,head->next是链表头)
 */
static inline void list_add_tail(struct list_head *_new, struct list_head *head)
{
	__list_add(_new, head->prev, head);
}

/* 用于让销毁的链表节点指向一个未使用地址 */
#define LIST_POISON	((void *)0x0)

/**
 * \brief	将一个链表节点从它自己所属的这条链表中删除
 * \param	entry:	要删除的链表节点
 */
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = (struct list_head *)LIST_POISON;
	entry->prev = (struct list_head *)LIST_POISON;
}

#endif /* LIST_H */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值