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

}

运行结果为:

[html] view plaincopyroot@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()检测,链表非空

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

[html] view plaincopystatic 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;

}

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

[cpp] view plaincopystatic 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后,重新初始化被去掉的空的链表头。这样的描述可能不是太好理解,接下来看看一段代码。

[html] view plaincopy#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;

}

运行结果为:

[html] view plaincopyroot@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内核双向循环链表实现最为巧妙的地方了。

[cpp] view plaincopy#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内核双向循环链表能够区别于传统链表的关键所在。可能看到这儿的时候读者已经感觉非常的枯燥了,但是别放弃,坚持看完,因为虽然这样的讲解枯燥了点,但是非常有用。所以坚持坚持吧!

[cpp] view plaincopy#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()已经不再感到陌生了。上面的但三个遍历链表的宏都类似,继续往下看。

[cpp] view plaincopy#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。也就是说你可以遍历完当前节点后将其删除,同时可以接着访问下一个节点,遍历完毕后就只剩下一个头节点。当然这有一个最为典型的应用,那就是我们在多进程编程时候,多个进程等待在同一个等待队列上,若事件发生时唤醒所有进程,则可以唤醒后将其依次从等待队列中删除。

[html] view plaincopy#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;

}

运行结果为:

[html] view plaincopyroot@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()宏来实现

[cpp] view plaincopy#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结构在宿主结构中的成员名。有时候做过多的讲解反而没有看看代码的效果好,我们还是用段代码来说明下吧。

[html] view plaincopy#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;

}

运行结果为:

[html] view plaincopyroot@ubuntu:/home/paixu/dlist_node# ./a

student num: 3 student name: Stu3

student num: 2 student name: Stu2

student num: 1 student name: Stu1

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

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

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

C语言游戏传递小秘密,C语言的那些小秘密之链表相关推荐

  1. c语言游戏程序ide,C语言编译器IDE

    C语言编译器IDE是一款非常专业的编程学习辅导软件,这个软件能够教会用户朋友们很多的代码信息,喜欢编程的小伙伴可以下载学习一下.这个C语言编译器IDE平台上的讲解非常详细,用户有不懂的知识点都可以在这 ...

  2. c语言游戏 人物素材,C语言打造——游戏场景里面的人物行走

    今天带来一个游戏场景的人物行走.用的是C语言写的,用到的编译器是vs2015,2013或者(vc)也行的,自然要记得安装图形库文件. 涉及到的知识点不多: 图形库 鼠标和键盘操作 图片 贴图 C语言 ...

  3. c语言游戏call调用,C语言-植物大战僵尸-刷僵尸call;fps游戏CS-方框透视(矩阵+传统)...

    C语言-植物大战僵尸-刷僵尸call:fps游戏CS-方框透视(矩阵+传统)前言:大家好,我是向上先生,嘿嘿~最近在学习逆向.反汇编等等,首先我在非常感谢52pojie这个平台,差不多很多学习资源都是 ...

  4. 猜数字小c语言游戏课程任务书,C语言课程设计猜数字游戏姚成.doc

    课 程 设 计 报 告 课程名称 C语言程序设计报告 课题名称 猜数字游戏 专 业 电气工程及其自动化 班 级 1104 学 号 23 姓 名 姚成 指导教师 黄晓宇 丁敬忠 谭小兰 2012年 6月 ...

  5. c语言游戏经典案例,C语言_编游戏案例精编.doc

    C语言_编游戏案例精编 C语言_编游戏案例精编 C语言 编游戏案例精编 案例一 贪吃蛇游戏 案例二 计算器 案例三 黑白棋游戏 案例四 迷宫问题 案例五 扫地雷游戏 案例六 速算24 案例七 数据结构 ...

  6. 简单c语语言游戏教案大全,大班语言活动教案大全简单趣味

    从小培养幼儿学说普通话的兴趣.同时家长应注意用规范的语言来与幼儿进行日常交谈. 以下是小编精心收集整理的幼儿园大班语言活动方案,下面小编就和大家分享,来欣赏一下吧. 幼儿园大班语言活动方案1 活动目标 ...

  7. c语言游戏如何分享,c语言怎么编游戏_编程语言分享讨论总汇吧吧_直线网_最专业的数字艺术学习交流平台...

    纯真童趣的<泡泡堂>,还有武林情仇,笑傲江湖的<剑侠情缘on line>.它是e时代常谈的话题,是交互式娱乐的主力军,是一种 高层次的综合艺术,更是一个民族的文化,世界观的全新 ...

  8. c语言游戏经典案例,C语言新案例-跑马游戏

    原标题:C语言新案例-跑马游戏 01 C语言新案例-跑马游戏 //今晚内容:C语言精简案例-跑马游戏 #include #include #include #include //定义宏参数:跑道长度. ...

  9. 如何下载c语言游戏,如何用C语言编写游戏.doc

    <如何用C语言编写游戏.doc>由会员分享,可在线阅读,更多相关<如何用C语言编写游戏.doc(71页珍藏版)>请在皮匠网上搜索. 1.如何用C语言编写游戏网络游戏是常谈的话题 ...

最新文章

  1. 安装完DevExpress14.2.5,如何破解它呢?
  2. 《Unity 游戏案例开发大全》一6.5 游戏主场景
  3. docker-compose运行sentry
  4. 【总结】循序渐进,有三AI不得不看的技术综述(超过100篇核心干货)
  5. boost::lambda模块ll_static_cast,ll_dynamic_cast,ll_const_cast,ll_reinterpret_cast的测试程序
  6. python plotly 多个表格_Plotly Python - 更改表格
  7. Spark1.4启动spark-shell时initializing失败
  8. Apache Kafka简介
  9. SAP License:SAP中的文本管理
  10. MYSQL8.0 排序查询报错 this is incompatible with sql_mode=only_full_group_by
  11. jni 结构体转对象_JVM系列(一)JVM启动流程和基本结构
  12. GJB 5000B-2021下载-见文章结尾
  13. 都市丽人2022春夏新品订货会“清春·焕境”惊艳开启
  14. Arduino循迹小车教程一----材料篇
  15. 快准狠的数据挖掘分析,用了这些方法!复旦 Zilliz 梦幻联动
  16. 【论文笔记】《SketchMate: Deep Hashing for Million-Scale Human Sketch Retrieval》
  17. 【Django】 gunicorn部署纪要
  18. 对于干涉仪二维测向的一些要点
  19. 晓说2017-定期持续更新
  20. camera中文版软件 ip_Synergy下载|Synergy 1.8.4 中文版

热门文章

  1. 产品经理经验谈100篇(三)-如何才能打造出一款好的SaaS产品?
  2. 复旦大学计算机保研经历,#易保研#经验分享 | 个人经历分享,圆梦复旦大学!...
  3. matlab pca可视化,利用Matlab实现PCA demo展示
  4. js 中对于 css 的变量操作(React也可)
  5. 使用FormData进行Ajax请求上传文件
  6. 使用HTML5实现地理位置定位
  7. JAVASCRIPT常用20种小技巧汇总
  8. border三角形阴影(不规则图形阴影)和多重边框的制作
  9. CSS基本知识(慕课网)
  10. 复习HTML CSS(2)