[原理分析]linux内核中的链表原理实践[2]
来源:互联网 发布:新一代人工智能发展 编辑:程序博客网 时间:2024/06/06 07:02
摘要:
本文过程化的演进方式,将自己写的链表结构慢慢地演化到类似linux内核链表的实现。
正文:
在本系列1中,如果将data_node中的信息调换一下,也即value放在前面,将head_node信息放在后面,那么节点数据就不能正常输出。
typedef struct data_node{int value;head_node h;}data_node;
要查找原因,主要还是看list_value函数的实现:
void list_value(data_node* d){ data_node* dn = d; do{ printf("%d ", dn->value); dn = (data_node*)dn->h.next; }while(dn!=d);}
上述代码是将dn->h.next的指针强制转化为data_node的指针完成data_node首地址的确定,现在data_node内部的数据调换顺序后,head_node的首地址不再是data_node的首地址,所以出错。此处的解决方法也很直接:通过head_node的地址反向求得data_node的地址,基本思路:将head_node的地址,减去其在data_node中的地址偏移,也就是data_node的地址了。按照此思路将list_value修改后的代码如下:
void show_value(head_node* ptr){head_node* temp = ptr;unsigned long offset =(unsigned long)&((data_node*)0)->h;data_node* dn = (data_node*)((unsigned long)ptr - offset);do{printf("%d ", dn->value);ptr = dn->h.next;unsigned long offset =(unsigned long)&((data_node*)0)->h;dn = (data_node*)((unsigned long)ptr - offset);}while(&(dn->h) != temp);}
求偏移时,采用下面的语句:(unsinged long) &((data_node*)0)->h;这里的求地址符号不能缺少,因为后面部分只是偏移到h,具体偏移的量还是也就是h所处的地址,因为地址是从0开始的;data_node的首地址就通过head_node的首地址,减去其在data_node中的偏移得到。上述的代码不清晰,我们将重复的部分写成宏的形式,那就有:
#define off() (unsigned long) (&((data_node*)0)->h)#define entry(ptr) (data_node*) ((unsigned long)ptr - off())
然后利用上述的宏再重写show_value函数:
void show_value(head_node* ptr){head_node* temp = ptr;data_node* dn = entry(ptr);do{printf("%d ", dn->value);ptr = dn->h.next;dn = entry(ptr);}while(&(dn->h) != temp);}
看上去精简不少,我们再把对数据处理的函数写成单独的模块:
void showme(daat_node* dn){ printf("%d ", dn->value);}
回头看上述的代码,还是不能满足我们模块化的需要,上述代码中的循环被数据处理模块割裂,不能很好地模块化,我们希望看到的代码是下面的这个样子,将循环部分的代码尽量集中在括号外面,带来的好处:循环部分代码复用性提升,修改处理函数变得容易;
data_node* dn;循环代码部分{ showme(dn);}
考察循环的常用三种写法:for, while, do while;do while已经被证明会被数据处理函数隔离,while的话,判断语句和操作语句的分离;最终只能考虑用for来实现我们上面的设想,将上述的show_value改成基于for循环的形式:
data_node* dn;for(head_node* temp = ptr, dn = entry(ptr); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr)){show_value(dn);}
这里犯了个小错误,需要将 head_node* temp的定义放在循环外面。于是代码变成如下的形式:
void show_value(head_node* ptr){data_node* dn = NULL;head_node* temp;for(temp = ptr, dn = entry(ptr); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr)){printf("%d ", dn->value);}}
运行上述的代码后发现并没有输出,仔细检查下主要在于语句:&(dn->h)!=temp;该语句在循环第一次执行时就不满足条件,因为temp当前的值就是dn->h的当前的地址,也就是说temp和dn属于同一个数据节点。当然在初始化时,可以让dn指向下一个ptr的下一个节点,但这样的问题在于不能输出ptr当前节点的数据,造成数据漏输。这就是for循环带来的问题,在原来的do while版本中,由于是先输出数据,更新ptr后,再做的判断,所以不会存在上述的问题。那此处怎么修改呢?简单想到的一个解决方法就是跳过第一个节点,第一个节点中不存数据,只是将第一个节点作为标志节点,也就是头节点。增加头节点的代码修改:
head_node* lh = (head_node*)malloc(sizeof(head_node));lh->next = lh;lh->prev = lh;node_add(lh, &(d1->h));node_add(lh, &(d2->h));node_add(lh, &(d3->h));
相应的show_value函数也要做对应修改:
void show_value(head_node* ptr){data_node* dn = NULL;head_node* temp;for(temp = ptr, dn = entry(ptr->next); &(dn->h)!=temp; ptr = dn->h.next, dn = entry(ptr)){showme(dn);}}
调用端的代码也要做对应修改:show_value(lh);经过测试,上述三个节点中的数据都能输出,但是看上去show_vlaue中的代码还是不够清晰,我们再打磨下:
void show_value(head_node* head){data_node* dn = NULL;head_node* pos;for(pos = head->next, dn = entry(pos); pos!=head; pos = pos->next, dn = entry(pos)){showme(dn);}}
上面的代码可以看到,for循环的操作和判断都移到大括号外面,括号里面只剩下操作函数,然后我们再将上述的for循环部分代码宏化:
#define list_each_entry(head, pos) for(pos = head->next, dn = entry(pos) ; \pos!=head; pos=pos->next, dn = entry(pos))
基于上述的宏,我们再修改show_value函数:
void show_value(head_node* head){data_node* dn = NULL;head_node* pos;list_each_entry(head, pos){showme(dn);}}
结束语:
本文以linux内核代码中的链表实现为灯塔,采用自然演化的过程,慢慢根据自己的需求,将自己的代码修改成类linux实现的代码。
- [原理分析]linux内核中的链表原理实践[2]
- [原理分析]linux内核中的链表原理实践[1]
- [原理分析]linux内核中的链表原理实践[3]
- Linux 内核中的 kconfig 原理
- linux内核链表分析与实践
- linux内核链表分析与实践
- linux内核链表分析与实践
- Linux内核等待队列机制原理分析
- EPOLL Linux内核源代码实现原理分析
- linux内核分析之-x86汇编原理
- Linux 内核工作原理
- Linux内核定时器原理
- linux内核mmap原理
- linux 内核编译原理
- Linux内核管理原理
- linux内核分析———SLAB原理及实现
- [Linux内核]从一个汇编分析计算机工作原理
- 20169219 linux内核原理与分析第二周作业
- 第13周项目1(2)倒序输出数组中的元素
- android 自动检测版本升级
- 深入理解java虚拟机——垃圾收集器和内存分配策略
- 【fafu】1003 数字接龙(排序意识小谈)
- 模板
- [原理分析]linux内核中的链表原理实践[2]
- 【转】让人深思的退役贴
- 深入理解java虚拟机——java内存区域
- tizen 技术 PPT 下载
- 专注做好一件事
- BZOJ 3732 Network Kruskal重构树
- docker image真相
- 第十三周项目五——字符串操作(1.2)
- linux 新建用户、用户组 以及为新用户分配权限