Linux作者Linus鄙视和推荐的指针用法是怎样的?

一、来源

酷壳上的这篇《LINUS:利用二级指针删除单向链表》文章里提到Linux作者Linus举的一个例子。我引用其中一部分中文翻译如下:

我见过很多人在删除一个单项链表的时候,维护了一个”prev”表项指针,然后删除当前表项,就像这样:

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

当我看到这样的代码时,我就会想“这个人不了解指针”。了解指针的人会使用链表头的地址来初始化一个“指向节点指针的指针”。当遍历链表的时候,可以不用任何条件判断:

*pp = entry->next

二、完整C代码

Linus并未提供完整代码,原博客中也引用了其他网友的代码,但是也不是完整代码,无法编译通过。
于是我参考了《二级指针实现单链表的插入、删除及 linux内核源码双向链表之奇技淫巧》这篇文章,写了一个完整版的代码,把Linus喜欢和不喜欢的版本都实现了一下,并且力图长得跟Linus的差不多(无法完全一样)。
完整C代码如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct node {
    int data;
    struct node *next;
} Node;

/** 插入节点,采用头插法 */
int insert(Node **pp, int v) {
    Node *t = (Node *) malloc(sizeof(Node));
    if (NULL == t) {
        return 0;
    }
    t->data = v;
    t->next = *pp;
    *pp = t;
    return 1;
}

/** 输出链表所有元素 */
void print(Node **pp) {
    for (Node *cur = *pp; cur;) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

/** 删除节点值为v的所有节点(Linus不喜欢的版本) */
void remove_that_linus_dislikes(Node **list_head, int v) {
    Node *entry = *list_head;
    Node *prev = NULL;
    while (entry) {
        Node *to_be_freed = NULL;
        if (entry->data == v) {
            to_be_freed = entry;
            if (prev) {
                prev->next = entry->next;
            } else {
                *list_head = entry->next;
            }
        } else {
            prev = entry;
        }
        entry = entry->next;
        if (to_be_freed) {
            free(to_be_freed);
        }
    }
}

/** 删除节点值为v的所有节点(Linus喜欢的版本) */
void remove_that_linus_likes(Node **pp, int v) {
    while (*pp) {
        Node *entry = *pp;
        if (entry->data == v) {
            Node *to_be_freed = *pp;
            *pp = entry->next; // 【关键代码】
            free(to_be_freed);
        } else {
            pp = &entry->next;
        }
    }
}

/** 初始化列表 */
Node *init_list() {
    Node *list_head = NULL;
    insert(&list_head, 4);
    insert(&list_head, 3);
    insert(&list_head, 3);
    insert(&list_head, 2);
    insert(&list_head, 2);
    insert(&list_head, 1);
    return list_head;
}

/** 测试Linux不喜欢的版本 */
void test_remove_that_linus_dislikes() {
    printf("---------------------- 分隔线 测试Linus不喜欢的版本:----------------------\n");

    Node *list_head = init_list();

    printf("        原链表:");
    print(&list_head);

    remove_that_linus_dislikes(&list_head, 1);
    printf(" 删除头(1)之后的链表:");
    print(&list_head);

    remove_that_linus_dislikes(&list_head, 4);
    printf(" 删除尾(4)之后的链表:");
    print(&list_head);

    remove_that_linus_dislikes(&list_head, 2);
    printf("删除中间(2)之后的链表:");
    print(&list_head);
}

/** 测试Linux喜欢的版本 */
void test_remove_that_linus_likes() {
    printf("---------------------- 分隔线 测试Linus喜欢的版本:----------------------\n");

    Node *list_head = init_list();

    printf("        原链表:");
    print(&list_head);

    remove_that_linus_likes(&list_head, 1);
    printf(" 删除头(1)之后的链表:");
    print(&list_head);

    remove_that_linus_likes(&list_head, 4);
    printf(" 删除尾(4)之后的链表:");
    print(&list_head);

    remove_that_linus_likes(&list_head, 2);
    printf("删除中间(2)之后的链表:");
    print(&list_head);
}

int main() {
    test_remove_that_linus_dislikes();
    printf("\n");
    test_remove_that_linus_likes();
    return 0;
}

三、打印结果

打印结果如下:

---------------------- 分隔线 测试Linus不喜欢的版本:----------------------
        原链表:1 2 2 3 3 4
 删除头(1)之后的链表:2 2 3 3 4
 删除尾(4)之后的链表:2 2 3 3
删除中间(2)之后的链表:3 3

---------------------- 分隔线 测试Linus喜欢的版本:----------------------
        原链表:1 2 2 3 3 4
 删除头(1)之后的链表:2 2 3 3 4
 删除尾(4)之后的链表:2 2 3 3
删除中间(2)之后的链表:3 3

四、两个版本对比

4.1 Linus不喜欢的版本

/** 删除节点值为v的所有节点(Linus不喜欢的版本) */
void remove_that_linus_dislikes(Node **list_head, int v) {
    Node *entry = *list_head;
    Node *prev = NULL;
    while (entry) {
        Node *to_be_freed = NULL;
        if (entry->data == v) {
            to_be_freed = entry;
            if (prev) {
                prev->next = entry->next;
            } else {
                *list_head = entry->next;
            }
        } else {
            prev = entry;
        }
        entry = entry->next;
        if (to_be_freed) {
            free(to_be_freed);
        }
    }
}

其中第9~13行力图跟Linus的描述保持一致,但是他演示的代码里面是list_head,而我用list_head无法实现,我用的是*list_head

意图是这样的:现在要删除当前节点entry,如果我的前面有节点,那就让前面节点的next指向我的后续节点;如果我的前面没有节点,那就让链表的头list_head指向我的后续节点。

坦白讲,作为C语言小白,为了实现这段被人鄙视的代码,我试错了好几次才写出来,写出来了还要被人鄙视。可能这就是普通人跟神仙的区别吧。


4.2 Linus喜欢的版本

/** 删除节点值为v的所有节点(Linus喜欢的版本) */
void remove_that_linus_likes(Node **pp, int v) {
    while (*pp) {
        Node *entry = *pp;
        if (entry->data == v) {
            Node *to_be_freed = *pp;
            *pp = entry->next; // 【关键代码】
            free(to_be_freed);
        } else {
            pp = &entry->next;
        }
    }
}

其中第7行是关键代码,解释如下:*pp里面存的值是当前节点entry的内存地址,既然当前节点要删除了,那我直接改这个值就行了,改成什么呢?当前是改成下一个节点的内存地址,即entry->next
不得不说真的很简洁。

其实这两种实现方式我都无法一眼看懂,都要靠调试之后才能真实清楚里面在干什么,所以如果你有疑惑的话,复制完整代码然后断点调试一下吧。

五、补充:Linus回答原文

Linus回答的原文在这里,搜索关键字Linus: Hmm可以直接定位到该回答,该回答的大意我理解如下:

  1. 我(指Linus本人)已经不写代码了,我的工作是看别人的代码,然后合并他们。
  2. 由于我要合并代码,我看得最多的不是好代码(cool),而是烂代码(broke),然后骂这些人。
  3. Linux内核里面确实有很多好代码,我尤其引以为傲的是文件查找缓存(filename lookup cache)。
  4. 凡人不必去看文件查找缓存的实现代码(因为太复杂而又太微妙了),但是还是应该理解二级指针(pointers-to-pointers)之类的底层代码。
  5. (然后举了我们本文的例子)
  6. 总结:把小细节做好是很值得骄傲的。

呵呵,看到第3条的时候我已经准备去找文件查找缓存的源码了,紧接着又意识到我其实就是第4条里面说的凡人~


全文完

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值