摘要:
本文的第[一,二]系列主题虽然是链表操作,内容还是指针的操作,本文通过链表实例来阐述下指针操作。不仅仅涉及到数据节点指针,也还涉及到函数指针,最后还涉及基于指针的函数体优化。
正文:
本文主要阐述链表中的节点删除操作,并且在删除过程中使用回调函数。回调函数本身也很简单,就是判断当前节点中数据的奇性或者偶性,来判断是否删除该数据节点。
#include "stdafx.h"
#include <memory>
typedef struct node{
int value;
node* next;
}node;
typedef bool (* condValid) (node* stu);
bool pickEven(node* n)
{
if((n->value) % 2 == 0)
return true;
return false;
}
bool pickOdd(node* n)
{
if((n->value) % 2 == 1)
return true;
return false;
}
node* addNode(node* head, int value)
{
if(head != NULL)
{
node* n = new node();
n->value = value;
n->next = head->next;
head->next = n;
}else{
head = new node();
head->next = NULL;
head->value = value;
}
return head;
}
node* delNode(node* head, condValid validRemove)
{
node* prev;
node* cur;
for(prev = NULL, cur = head; cur; ){
node* const next = cur->next;
if(validRemove(cur)){
if(prev){
prev->next = next;
}else{
head = next;
}
free(cur);
}else{
prev = cur;
}
cur = next;
}
return head;
}
void showList(node* head)
{
for(;head; head=head->next)
printf("%d ", head->value);
printf("\n");
}
void testAdd()
{
node* head = NULL;
head = addNode(head, 2);
head = addNode(head, 3);
head = addNode(head, 5);
head = addNode(head, 6);
showList(head); //it should show 2, 6, 5, 3;
}
void testDel()
{
node* head = NULL;
head = addNode(head, 2);
head = addNode(head, 3);
head = addNode(head, 5);
head = addNode(head, 6);
showList(head); //it should show 2, 6, 5, 3;
node* head1 = delNode(head,pickEven);
printf("after delNode using isEvenSel:\n");
showList(head1); //it should show 5, 3;
node* head2 = delNode(head1, pickOdd);
printf("after delNode using isOddSel:\n");
showList(head2); //it shoud show nothing.
}
int _tmain(int argc, _TCHAR* argv[])
{
//testAdd();
testDel();
getchar();
return 0;
}</span>
上面的delNode函数需要注意的是在for语句中:1.for(prev = NULL, cur= head; cur; ){后面并没有马上跟上常见的cur=cur->next;2. 在循环内部一开始就采用将cur->next保存下来;1和2的原因都在于cur当前节点可能在循环中被删除。上述的delNode的唯一不足在于,每次需要返回head节点;如何做到不用返回变更后的head值呢?很直接的我们会想到指针。于是,我们将head的地址作为参数传入,具体的代码如下:
void delNode2(node** head, condValid validRemove)
{
node** cur;
node* entry;
for(cur=head;*cur; ){
entry = *cur;
if(validRemove(entry)){
*cur = entry->next;
free(entry);
}else{
cur = &entry->next;
}
}
}
void testDel2()
{
node* head = NULL;
head = addNode(head, 2);
head = addNode(head, 3);
head = addNode(head, 5);
head = addNode(head, 6);
showList(head); //it should show 2, 6, 5, 3;
delNode2(&head,pickOdd);
showList(head); //it should shoe 2, 6;
}
上述代码的关键在于*cur = entry->next是覆盖,而cur=&entry->next则是移动,具体的流程可以参照下图:
下面我们再来看个基于指针的优化,所摘的代码来源于linux2.6早期版本的网络部分,此处不需要理解代码背后的网络原理,只需要就代码论代码就可以说明白其中所包含的优化原理,那么首先来看下面的示例代码(linux中原代码的简化实现):
for (ptype = ptype_base; ptype != NULL; ptype = ptype->next)
{
if ((ptype->type == type || ptype->type == htons(ETH_P_ALL)) && (!ptype->dev || ptype->dev==skb->dev)){
struct sk_buff *skb2;
skb2=skb_clone(skb, GFP_ATOMIC);
if(skb2)
ptype ->func(skb2, skb->dev, ptype);
}
}
kfree_skb(skb, FREE_WRITE);
光从代码的表面可以理解,其主要的目的是遍历某个链表,找出其中满足条件的节点进行深度拷贝(skb_clone),将拷贝后的节点作相应的处理,最后再将原始节点skb释放掉;上述代码的主要问题在于skb_clone操作是非常费时的,那有没有办法可以减少clone操作次数呢?可以利用的就是skb,假设这里要拷贝10次,那么最后一次就可以认为是不必要的,可以直接利用skb,因为反正不用白不用,马上skb就会被kfree_skb函数销毁;即使用了,因为skb不会再用作拷贝的原型(最后一次),所以直接利用skb进行ptype->func()操作是合理的。问题是如何免除最后一次的skb2拷贝,直接用skb来实现ptype->func()操作呢?参考linux社区中内核黑客的解决方案:通过增加额外的局部变量,实现延迟type->func()的处理。
修改1: 增加用于暂存ptype的局部变量:pt_prev = NULL;
修改2:对for循环中的if语句进行相应的修改:
if(pt_prev)
{
struct sk_buff *skb2;
skb2=skb_clone(skb, GFP_ATOMIC);
if(skb2)
pt_prev ->func(skb2, skb->dev,pt_prev);
}
pt_prev = ptype;
修改3:在退出for循环后,处理掉最后的一个ptype:
<span style="font-size:14px;">if(pt_prev)
pt_prev->func(skb, skb->dev, pt_prev);
else
kfree_skb(skb, FREE_WRITE);</span>
整合上述的代码修改,最后可以得到:
pt_prev = NULL;
for (ptype = ptype_base; ptype != NULL; ptype = ptype->next)
{
if ((ptype->type == type || ptype->type == htons(ETH_P_ALL)) && (!ptype->dev || ptype->dev==skb->dev))
{
if(pt_prev)
{
<span style="white-space:pre"> </span>struct sk_buff *skb2;
skb2=skb_clone(skb, GFP_ATOMIC);
if(skb2)
pt_prev ->func(skb2, skb->dev, pt_prev);
}
pt_prev = ptype;
}
}//end of loop;
if(pt_prev)
pt_prev->func(skb, skb->dev, pt_prev);
else
kfree_skb(skb, FREE_WRITE);
启发:
1. 上述代码中展示一种延迟处理的代码技巧,虽然扣出性能,但是程序的实现复杂度提升,代码不容易读懂;
2. 由于操作系统优化的需要,linux实现中肯定包含很多上述的代码优化措施,作为学习者需要有一定的代码简化能力和思考追问能力;
参考资料:
http://coolshell.cn/articles/8990.html
http://coolshell.cn/articles/9859.html