Linus Torvalds - 从单链表中删除节点

From: http://www.csdn.net/article/2013-01-10/2813559-two-star-programming

From: http://wordaligned.org/articles/two-star-programming


几周前, Linus Torvalds在Slashdot上回答了一些问题。其中有一条引发了开发者们的强烈关注,当被问到他心目中的内核开发者时,他说自己这些日子已经不怎么看代码了,除非是帮别人审查。他稍微暂停了一下,坦言那些“狡猾”的通过文件名查找高速缓存又抱怨自己能力一般的内核“恶魔”(黑客)才是他欣赏的。

他说:

我真的希望更多人能理解真正核心的低层代码。不是无锁名字查找那种大而复杂的代码,只是正确的使用指针的指针而已。比如,我曾看见过许多人通过跟踪上一页条目删除一个单向链接的列表项,然后删除该条目。例如:

  
  
  1. if (prev)  
  2.     prev->next = entry->next;  
  3. else  
  4.     list_head = entry->next; 

每当我看到这些的代码,我会说:“此人不了解指针”。这还是一个可悲的、常见的问题。

如果开发者能够理解指针,只需要使用“指向该条目的指针”并初始化list_head,然后贯穿列表,此时无需使用任何条件语句即可删除该条目,只需通过 *pp = entry->next。

我想我理解指针,但不幸的是,如果要实现删除函数,我会一直保持跟踪前面的列表节点。这里是代码草稿:

不理解指针的人做法:

 
 
  1. typedef struct node  
  2. {  
  3.     struct node * next;  
  4.     ....  
  5. } node;  
  6.  
  7. typedef bool (* remove_fn)(node const * v);  
  8.  
  9. // Remove all nodes from the supplied list for which the   
  10. // supplied remove function returns true.  
  11. // Returns the new head of the list.  
  12. node * remove_if(node * head, remove_fn rm)  
  13. {  
  14.     for (node * prev = NULL, * curr = head; curr != NULL; )  
  15.     {  
  16.         node * next = curr->next;  
  17.         if (rm(curr))  
  18.         {  
  19.             if (prev)  
  20.                 prev->next = curr->next;  
  21.             else  
  22.                 head = curr->next;  
  23.             free(curr);  
  24.         }  
  25.         else  
  26.             prev = curr;  
  27.         curr = next;  
  28.     }  
  29.     return head;  

这个链表很简单,但可以把每个节点的指针和sentinel值构建成了一个完美的结构体,但是修改这个表的代码需要很精妙。难怪链表功能会常出现在许多面试环节中。

上面执行的代码是处理从列表头中删除任何节点所需的条件。

现在,让我们好好记住Linus Torvalds执行代码。在这种情况下,我们通过一个指针指向列表头来贯穿列表遍历修改。

Two star programming:

 
 
  1. void remove_if(node ** head, remove_fn rm)  
  2. {  
  3.     for (node** curr = head; *curr; )  
  4.     {  
  5.         node * entry = *curr;  
  6.         if (rm(entry))  
  7.         {  
  8.             *curr = entry->next;  
  9.             free(entry);  
  10.         }  
  11.         else  
  12.             curr = &entry->next;  
  13.     }  

好多了!最关键的部分在于:链表中的链接都是指针,因此指针到指针是修改链表的首选方案。

改进版的remove_if()是一个使用双重星号的例子,双重星号象征着两重间接寻址,再加一个星(third star)又会太过多余。


English source:

A few weeks ago Linus Torvalds answered some questions on slashdot. All his responses make good reading but one in particular caught my eye. Asked to describe his favourite kernel hack, Torvaldsgrumbles he rarely looks at code these days — unless it’s to sort out someone else’s mess. He then pauses to admit he’s proud of the kernel’s fiendishly cunning filename lookup cache before continuing to moan about incompetence.

At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I’ve seen too many people who delete a singly-linked list entry by keeping track of theprev entry, and then to delete the entry, doing something like

if (prev)
    prev->next = entry->next;
else
    list_head = entry->next;

and whenever I see code like that, I just go “This person doesn’t understand pointers”. And it’s sadly quite common.

People who understand pointers just use a “pointer to the entry pointer”, and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a*pp = entry->next.

Well I thought I understood pointers but, sad to say, if asked to implement a list removal function I too would have kept track of the previous list node. Here’s a sketch of the code:

This person doesn’t understand pointers
typedef struct node
{
    struct node * next;
    ....
} node;

typedef bool (* remove_fn)(node const * v);

// Remove all nodes from the supplied list for which the 
// supplied remove function returns true.
// Returns the new head of the list.
node * remove_if(node * head, remove_fn rm)
{
    for (node * prev = NULL, * curr = head; curr != NULL; )
    {
        node * const next = curr->next;
        if (rm(curr))
        {
            if (prev)
                prev->next = next;
            else
                head = next;
            free(curr);
        }
        else
            prev = curr;
        curr = next;
    }
    return head;
}

The linked list is a simple but perfectly-formed structure built from nothing more than a pointer-per-node and a sentinel value, but the code to modify such lists can be subtle. No wonder linked lists feature in so many interview questions!

The subtlety in the implementation shown above is the conditional required to handle any nodes removed from the head of the list.

Now let’s look at the implementation Linus Torvalds had in mind. In this case we pass in a pointer to the list head, and the list traversal and modification is done using a pointer to the next pointers.

Two star programming
void remove_if(node ** head, remove_fn rm)
{
    for (node** curr = head; *curr; )
    {
        node * entry = *curr;
        if (rm(entry))
        {
            *curr = entry->next;
            free(entry);
        }
        else
            curr = &entry->next;
    }
}

Much better! The key insight is that the links in a linked list are pointers and so pointers to pointers are the prime candidates for modifying such a list.

§

The improved version of remove_if() is an example of two star programming: the doubled-up asterisks indicate two levels of indirection. A third star would be one too many.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值