c语言中listempty函数,list_empty()和list_empty_careful()

大多数的读者在学习编程语言的时候都不喜欢那些枯燥的文字描述,包括我自己在开始学习编程的时候也是这样,对于代码的热情远远高于文字,所以我在我写东西的时候也不喜欢用枯燥的文字描述来向读者讲解,更喜欢用代码加上适当的文字描述的方式进行讲解,因为有些东西可能用枯燥的文字描述半天还不如实实在在的给读者呈现出一段简单的代码,让读者理解得更加的透彻些。但是并不是说文字描述就没用,文字描述也很重要,只是绝大部分读者都更加的希望直接达到最终的效果,都想跳过那些中间的步骤。接下来我们接着上一篇博客《C语言的那些小秘密之链表(三)》的内容继续讲解linux内核双向循环链表。

特此说明:我会把我在文章中编写代码时候用到的头文件list.h上传到我的空间,免积分下载,有需要的读者可以自己去下载,当然也可以自己上网下载或者从自己安装的linux系统中得到。下载地址:http://download.csdn.net/user/bigloomy

static inline int list_empty(const struct list_head *head)

{

return head->next == head;

}

static inline int list_empty_careful(const struct list_head *head)

{

struct list_head *next = head->next;

return (next == head) && (next == head->prev);

}

list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。但是稍有区别的就是第一个链表使用的检测方法是判断表头的结点的下一个结点是否为其本身,如果是则返回为true,否则返回false。第二个函数使用的检测方法是判断表头的前一个结点和后一个结点是否为其本身,如果同时满足则返回false,否则返回值为true。说多了可能读者就会没耐心了,那么接下来我来看看下面的代码。

#include

#include

#include "list.h"

typedef struct _stu

{

char name[20];

int num;

struct list_head list;

}stu;

int main()

{

stu *pstu;

stu *tmp_stu;

struct list_head stu_list;

struct list_head *pos;

int i = 0;

INIT_LIST_HEAD(&stu_list);

pstu = malloc(sizeof(stu)*5);

for(i=0;i<5;i++)

{

sprintf(pstu[i].name,"Stu%d",i+1);

pstu[i].num = i+1;

list_add( &(pstu[i].list), &stu_list);

}

list_for_each(pos,&stu_list)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

}

if(list_empty(&stu_list))

printf("使用list_empty()检测,链表为空\n");

else

printf("使用list_empty()检测,链表非空\n");

if(list_empty_careful(&stu_list))

printf("使用list_empty_careful()检测,链表为空\n");

else

printf("使用list_empty_careful()检测,链表非空\n");

free(pstu);

return 0;

}

运行结果为:

root@ubuntu:/home/paixu/dlist_node# ./a

student num: 5       student name: Stu5

student num: 4       student name: Stu4

student num: 3       student name: Stu3

student num: 2       student name: Stu2

student num: 1       student name: Stu1

使用list_empty()检测,链表非空

使用list_empty_careful()检测,链表非空

看看代码就知道如何使用了,接下来看看链表的合成。

static inline void __list_splice(struct list_head *list,

struct list_head *head)

{

struct list_head *first = list->next;

struct list_head *last = list->prev;

struct list_head *at = head->next;

first->prev = head;

head->next = first;

last->next = at;

at->prev = last;

}

这个地方我觉得最好的方法就是使用图示来进行讲解,直观易懂,如果用文字描述半天还不如读者看一眼图。

将一个链表插入到另外一个链表中。不作链表是否为空的检查,由调用者默认保证。因为每个链表只有一个头节点,将空链表插入到另外一个链表中是没有意义的。但被插入的链表可以是空的。

static inline void list_splice(struct list_head *list, struct list_head *head)

{

if (!list_empty(list))

__list_splice(list, head);

}

在这种情况下会丢弃list所指向的头结点,因为两个链表有两个头结点,所以我们必须要去掉其中一个头结点。只要list非空链,head无任何限制,该函数就能实现链表的合并。

static inline void list_splice_init(struct list_head *list,

struct list_head *head)

{

if (!list_empty(list)) {

__list_splice(list, head);

INIT_LIST_HEAD(list);

}

}

以上函数的功能是将一个链表list的有效信息合并到另外一个链表head后,重新初始化被去掉的空的链表头。这样的描述可能不是太好理解,接下来看看一段代码。

#include

#include

#include "list.h"

typedef struct _stu

{

char name[20];

int num;

struct list_head list;

}stu;

int main()

{

stu *pstu,*pstu2;

stu *tmp_stu;

struct list_head stu_list,stu_list2;

struct list_head *pos;

int i = 0;

INIT_LIST_HEAD(&stu_list);

INIT_LIST_HEAD(&stu_list2);

pstu = malloc(sizeof(stu)*3);

pstu2 = malloc(sizeof(stu)*3);

for(i=0;i<3;i++)

{

sprintf(pstu[i].name,"Stu%d",i+1);

sprintf(pstu2[i].name,"Stu%d",i+4);

pstu[i].num = i+1;

pstu2[i].num = i+4;

list_add( &(pstu[i].list), &stu_list);

list_add( &(pstu2[i].list), &stu_list2);

}

printf("stu_list  链表\n");

list_for_each(pos,&stu_list)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

}

printf("stu_list2 链表\n");

list_for_each(pos,&stu_list2)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

}

printf("stu_list链表和stu_list2 链表合并以后\n");

list_splice(&stu_list2,&stu_list);

list_for_each(pos,&stu_list)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

}

free(pstu);

return 0;

}

运行结果为:

root@ubuntu:/home/paixu/dlist_node# ./a

stu_list  链表

student num: 3       student name: Stu3

student num: 2       student name: Stu2

student num: 1       student name: Stu1

stu_list2 链表

student num: 6       student name: Stu6

student num: 5       student name: Stu5

student num: 4       student name: Stu4

stu_list链表和stu_list2 链表合并以后

student num: 6       student name: Stu6

student num: 5       student name: Stu5

student num: 4       student name: Stu4

student num: 3       student name: Stu3

student num: 2       student name: Stu2

student num: 1       student name: Stu1

有了直观的代码和运行结果,理解起来也更加的容易了。

有了上面的这些操作,但是我们还一直没有讲到我们最终所关心的宿主结构,那么接下来我们一起来看看我们该如何取出宿主结构的指针呢?这也是我认为linux内核双向循环链表实现最为巧妙的地方了。

#define list_entry(ptr, type, member)  \

((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

看看上面的代码,发现一个很熟悉的身影(unsigned long)(&((type *)0)->member)),这个我在前一篇博客《C语言的那些小秘密之字节对齐》中已经讲解过了,多以在此就不再做过多的讲解,如果有不明白的读者可以回过去看看讲解再回过来阅读。通过(unsigned long)(&((type *)0)->member))我们得出了成员变量member的偏移量,而ptr为指向member的指针,因为指针类型不同的原因,所以我们再次要先进行(char*)的转换之后再进行计算。所以我们用ptr减去member的偏移量就得到了宿主结构体的指针,这就是一个非常巧妙的地方,这也就使得linux内核双向循环链表能够区别于传统链表的关键所在。可能看到这儿的时候读者已经感觉非常的枯燥了,但是别放弃,坚持看完,因为虽然这样的讲解枯燥了点,但是非常有用。所以坚持坚持吧!

#define list_for_each(pos, head) \

for (pos = (head)->next; prefetch(pos->next), pos != (head); \

pos = pos->next)

#define __list_for_each(pos, head) \

for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_prev(pos, head) \

for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \

pos = pos->prev)

遍历是双循环链表的基本操作,head为头节点,遍历过程中首先从(head)->next开始,当pos==head时退出,故head节点并没有访问,这和链表的结构设计有关,通常头节点都不含有其它有效信息,因此可以把头节点作为双向链表遍历一遍的检测标志来使用。在list_for_each宏中读者可能发现一个比较陌生的面孔,我们在此就不将prefetch展开了讲解了,有兴趣的读者可以自己查看下它的实现,其功能是预取内存的内容,也就是程序告诉CPU哪些内容可能马上用到,CPU预先其取出内存操作数,然后将其送入高速缓存,用于优化,是的执行速度更快。接下来的__list_for_each()宏和list_for_each_prev()宏就不在此做一一的讲解了,和list_for_each()宏类似。 就是遍历的方向有所改变或者不使用预取。

通过上面的讲解和前面那么多的代码,相信读者这个时候对于list_for_each()已经不再感到陌生了。上面的但三个遍历链表的宏都类似,继续往下看。

#define list_for_each_safe(pos, n, head) \

for (pos = (head)->next, n = pos->next; pos != (head); \

pos = n, n = pos->next)

以上list_for_each_safe()宏也同样是用于遍历的,不同的是里边多出了一个n暂存pos下一个节点的地址,避免了因为pos节点被释放而造成的断链,这也就体现出了safe。也就是说你可以遍历完当前节点后将其删除,同时可以接着访问下一个节点,遍历完毕后就只剩下一个头节点。当然这有一个最为典型的应用,那就是我们在多进程编程时候,多个进程等待在同一个等待队列上,若事件发生时唤醒所有进程,则可以唤醒后将其依次从等待队列中删除。

#include

#include

#include "list.h"

typedef struct _stu

{

char name[20];

int num;

struct list_head list;

}stu;

int main()

{

stu *pstu;

stu *tmp_stu;

struct list_head stu_list;

struct list_head *pos,*n;

int i = 0;

INIT_LIST_HEAD(&stu_list);

pstu = malloc(sizeof(stu)*3);

for(i=0;i<3;i++)

{

sprintf(pstu[i].name,"Stu%d",i+1);

pstu[i].num = i+1;

list_add( &(pstu[i].list), &stu_list);

}

printf("通过list_for_each_safe()遍历使用list_del(pos)删除结点前\n");

list_for_each_safe(pos, n, &stu_list)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

list_del(pos);

}

printf("通过list_for_each()遍历使用list_del(pos)删除结点后\n");

list_for_each(pos,&stu_list)

{

tmp_stu = list_entry(pos, stu, list);

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

}

free(pstu);

return 0;

}

运行结果为:

root@ubuntu:/home/paixu/dlist_node# ./a

通过list_for_each_safe()遍历使用list_del(pos)删除结点前

student num: 3       student name: Stu3

student num: 2       student name: Stu2

student num: 1       student name: Stu1

通过list_for_each()遍历使用list_del(pos)删除结点后

读者可以结合运行结果再去阅读上面的文字描述部分。

如果只提供对list_head结构的遍历操作是远远不够的,我们希望实现的是对宿主结构的遍历,即在遍历时直接获得当前链表节点所在的宿主结构项,而不是每次要同时调用list_for_each()和list_entry()。为此Linux特地提供了list_for_each_entry()宏来实现

#define list_for_each_entry(pos, head, member)                                \

for (pos = list_entry((head)->next, typeof(*pos), member);        \

prefetch(pos->member.next), &pos->member != (head);         \

pos = list_entry(pos->member.next, typeof(*pos), member))

第一个参数为传入的遍历指针,指向宿主数据结构,第二个参数为链表头,为list_head结构,第三个参数为list_head结构在宿主结构中的成员名。有时候做过多的讲解反而没有看看代码的效果好,我们还是用段代码来说明下吧。

#include

#include

#include "list.h"

typedef struct _stu

{

char name[20];

int num;

struct list_head list;

}stu;

int main()

{

stu *pstu;

stu *tmp_stu;

struct list_head stu_list;

struct list_head *pos,*n;

int i = 0;

INIT_LIST_HEAD(&stu_list);

pstu = malloc(sizeof(stu)*3);

for(i=0;i<3;i++)

{

sprintf(pstu[i].name,"Stu%d",i+1);

pstu[i].num = i+1;

list_add( &(pstu[i].list), &stu_list);

}

list_for_each_entry(tmp_stu, &stu_list, list)

printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

free(pstu);

return 0;

}

运行结果为:

root@ubuntu:/home/paixu/dlist_node# ./a

student num: 3       student name: Stu3

student num: 2       student name: Stu2

student num: 1       student name: Stu1

如果读者一开始对于文字描述感到陌生的话,那么就再次结合代码去阅读。

接下来再来看看最后几个。

#define list_for_each_entry_reverse(pos, head, member)                        \

for (pos = list_entry((head)->prev, typeof(*pos), member);        \

prefetch(pos->member.prev), &pos->member != (head);         \

pos = list_entry(pos->member.prev, typeof(*pos), member))

#define list_prepare_entry(pos, head, member) \

((pos) ? : list_entry(head, typeof(*pos), member))

#define list_for_each_entry_continue(pos, head, member)                 \

for (pos = list_entry(pos->member.next, typeof(*pos), member);        \

prefetch(pos->member.next), &pos->member != (head);        \

pos = list_entry(pos->member.next, typeof(*pos), member))

#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))

以上几个与list_for_each_entry类似,只是其中略有差别,list_prepare_entry()中含有prefetch(),它的作用在上面已经讲解,有什么疑惑可以返回去阅读下,在此不再做过多的讲解;list_for_each_entry_continue()和list_for_each_entry()的区别主要是list_for_each_entry_continue()可以不从链表头开始遍历,而是从已知的某个pos结点的下一个结点开始遍历。在某些时候如果不是从头结点开始遍历,那么为了保证pos的初始值有效,必须使用list_prepare_entry()。其含义就是如果pos非空,那么pos的值就为其本身,如果pos为空,那么就从链表头强制扩展一个虚pos指针,读者自己分析list_prepare_entry()的实现就明白了。list_for_each_entry_safe()要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的宿主结构体的地址,避免因pos节点被释放而造成的断链。

到此我们linux内核双向循环链表的旅程就结束了,下一篇我们将开始一个新的旅程。由于本人水平有限,博客中的不妥或错误之处在所难免,殷切希望读者批评指正。同时也欢迎读者共同探讨相关的内容,如果乐意交流的话请留下你宝贵的意见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/411337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

总是忘记的oracle 函数

怪自己总不操心&#xff0c;总有那么一些 常用函数的用法忘的一干二净&#xff0c;为了提高以后自己的工作效率&#xff0c;还是老老实实记录下来吧&#xff01; 日期类型函数 1.日期计算 Oracle 默认对日期是按“天”单位进行计算的&#xff0c;所以对日其他时分秒计算法如下&…

[html] 写一个水平竖直居中的弹窗,带遮罩层的布局

[html] 写一个水平竖直居中的弹窗&#xff0c;带遮罩层的布局 <div class"container"><div class"modal">modal<div> </div>个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷…

管道抛光防锈机器人_全国首创!嵊州企业的这项防锈技术用在了雪龙号上

2019-03-26 14:22 | 浙江新闻客户端 | 记者 金汉青 通讯员 胡吉图片来源于视觉中国近日&#xff0c;嵊州春凯新材料有限公司收到了来自上海铁路局的一批订单&#xff0c;对方要求春凯新材料对合肥工务段的一座高铁桥作钢铁除锈防锈处理。据了解&#xff0c;这将是春凯新材料研发…

怎么用c语言实现万年历,用C语言如何编写“万年历”

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼程序分析&#xff1a;以3月5日为例&#xff0c;应该先把前两个月的加起来&#xff0c;然后再加上5天即本年的第几天&#xff0c;特殊情况&#xff0c;闰年且输入月份大于3时需考虑多加一天。 8 G\?!^ug1GER 2.程序源代码&#xff…

一个程序设计试题:读取2维字符数组,判断出表示的数字

有这样的字符矩阵&#xff0c;它可以表示1,2,3,4,5,6,7,8,9,0任意一个数字。如下所示&#xff1a;1&#xff1a;. . .. . |. . |2: . _ .. _ || _ .3: . _ .. _ |. _ |4: . . .| _ |. . |5: . _ .| _ .. _ |6: . _ .| _ .| _ |7: . _ .. . |. . |8: . _ .| _ || _ |9: . _ .| _…

jzoj4640. 【GDOI2017模拟7.15】妖怪

Description Input Output Sample Input 3 1 1 1 2 2 2 Sample Output 8.0000 Data Constraint 题解 我还挺喜欢数学的呢 这题一眼看上去不会&#xff0c;化化式子没想到未知数竟然是一个反比例一次函数的样子。 长这样&#xff1a;axbx\frac a xbxxa​bx 当时心态就没了。 原来…

[html] H5如何与APP交互?有哪些方式?

[html] H5如何与APP交互&#xff1f;有哪些方式&#xff1f; jsbridge与app通信个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

电脑w ndows无法自动修复,windows 10自动修复无法修复你的电脑

我是由8.1升级到的10&#xff0c;为了以防万一&#xff0c;又做了个10的镜像。我发现不管是winpe还是windows10安装镜像里面修复计算机中自动修复一直处于无法修复计算机的状态&#xff0c;在日志文件中---------------------------尝试修复次数:1会话详细信息----------------…

activiti 设置可选处理人_新品速递|高端系列!慧明DF系列线性相位处理专业音箱处理器...

2020年对很多人来说&#xff0c;是特别艰难的一年&#xff0c;一场新冠疫情&#xff0c;可谓把整个演艺行业压得喘不过气&#xff0c;但是尽管如此&#xff0c;我们仍坚决相信行业将会很快复苏&#xff0c;一切终将迎来新的生机与活力。2020年初&#xff0c;哪怕是最困难的时期…

Scrapy 教程(十)-管道与数据库

Scrapy 框架将爬取的数据通过管道进行处理&#xff0c;即 pipelines.py 文件。 管道处理流程 一、定义 item item 表示的是数据结构&#xff0c;定义了数据包括哪些字段 class TianqiItem(scrapy.Item):# define the fields for your item here like:city scrapy.Field() …

[html] 写页面布局时需要考虑哪些方面的因素?

[html] 写页面布局时需要考虑哪些方面的因素&#xff1f; 1 熟练使用块级元素 和 行内元素 2 html 结构语义化 3 是否考虑兼容性 和 移动端个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与…

地壳中元素含量排名记忆口诀_广州地化所等发现洋内弧大陆地壳成熟新机制

大陆的形成和演化是地球科学研究中广泛关注的前沿科学问题之一。大陆地壳总体上具有安山质到英安质地球化学成分特征&#xff0c;与洋内弧中大洋俯冲作用形成的岩浆岩具有相似的微量元素地球化学特征(如富集大离子亲石元素和亏损高场强元素)&#xff0c;众多学者认为大陆地壳的…

c#语言 修改xml文件路径,C#对XML操作:编辑XML文件内容-.NET教程,C#语言

public void page_load(object src,eventargs e){/** 版 权 &#xff1a; copyright by sem it department* 版 本 &#xff1a; version 0.0.1* 文 件 &#xff1a; editxml.aspx* 用 途 &#xff1a; 编辑xml文件(修改、删除行)* 作 者 &#xff1a; 欧阳云天 2005-4-9* 邮 箱…

创业

最近非常的忙。 9月10号需要给3个客户交付一套软件。而且公司也没什么人可以帮忙。大家都有自己的事情。只剩下不到一个月的时间了。还要兼顾测试。真的很担心。心情稍微好了些。有人帮忙了。 呵呵2009/8/25转载于:https://www.cnblogs.com/smallvv/archive/2009/08/17/1547927…

三星+android7.0+字体,三星S7升级安卓7.0新技能:新增分辨率调整功能

三星Galaxy Note 7推出了安卓7.0的升级&#xff0c;此次升级后&#xff0c;三星S7将会增加分辨率调整功能。这个功能可以根据用户的需求和喜好来进行调整&#xff0c;在HD(7201280像素)、FHD(12801920像素)以及QHD(14402560像素)三种不同的分辨率之间自由切换。在过去的一年里&…

html title属性无效_【学习教程】使用JavaScript删除CSS属性

方法1&#xff1a;使用CSS removeProperty&#xff1a;该CSSStyleDeclaration.removeProperty()方法被用来从一个元件的样式删除一个属性。通过遍历styleSheets数组并选择cssRule&#xff0c;可以选择元素的样式。然后可以使用要删除的属性指定removeProperty方法。句法&#x…

090817问题

问题编号&#xff1a;090817.UserControl 分类&#xff1a;用户控件 问题描述&#xff1a; 用户控件A功能描述&#xff1a;提交一组数据到服务器保存&#xff0c;成功保存地情况下呈现数据。控件上面至少存在一个button按钮&#xff0c;以及button单击事件。 页面需要罗列出已添…

[html] 你认为写出什么样的html代码才是好代码呢?

[html] 你认为写出什么样的html代码才是好代码呢&#xff1f; 但是发现是我自己写的时候。 我就知道&#xff0c;我的代码能力又上一层楼。 但是一个好的代码&#xff0c;不仅仅是逻辑顺序清楚&#xff0c;更重要还是复用性强&#xff0c;注释写的够让人明白。 其中最后一条尤…

添加系统路径

export PATH$PATH:/usr/local/mongodb/bin/ source /etc/profile 转载于:https://www.cnblogs.com/xiaobiaomei/p/10922225.html

WCF演练源代码

/Files/litaocnblogs/LTWCF.rar转载于:https://www.cnblogs.com/litaocnblogs/archive/2009/08/20/1550669.html