实现链表的两种方法:哪种更好?

发布于 2024-10-05 13:11:18 字数 1385 浏览 3 评论 0原文

我通常知道用 C 语言设计通用链表数据结构的两种方法。我想知道哪种方法更好。在提出问题之前,我将简要介绍这两种方法:

一种方法是围绕如下结构构建函数:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
    void *data;
};

显然,数据指针指向有效负载。列表元素结构位于有效负载之外。例如,glib 就是这样设计其双链表功能的: http://library.gnome.org/devel/glib/2.26/glib-Doubly-Linked-Lists.html

另一种方法是在 Linux 内核中完成的方式: http://isis.poly.edu/kulesh/stuff/src/klist/。列表元素结构中没有指向有效负载的空指针。相反,列表元素结构包含在有效负载结构中:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
};

struct person {
    char name[20];
    unsigned int age;
    struct list_element list_entry;
};

使用一个特殊的宏来获取指向有效负载结构的指针,给定一个指向 list_entry 的指针、有效负载结构中的名称和有效负载结构的类型(list_entry()宏)。

最后,这里有一个问题:这两种构造链表的方法中,后者的优点是什么?有几次我听到人们说第二个比第一个更“通用”,但为什么呢?我什至认为第一种方法更通用,因为有效负载结构与列表实现无关,而第二种方法则不然。
第二种方法的另一个缺点是,如果您想将有效负载放置在多个列表中,则应该为有效负载结构中的每个列表都有一个 struct list_element 成员。

编辑: 总结到目前为止,我看到了两个对我来说很重要的答案:

  • 使用第一种方法:从列表中删除有效负载涉及循环遍历整个列表,直到找到指向有效负载的列表元素。您不需要使用第二种方法来执行此操作。 (Patrick 的回答)
  • 使用第一种方法,您必须为每个元素执行两次 malloc():一个用于有效负载,一个用于列表元素结构。对于第二种方法,一个 malloc() 就足够了。 (罗迪的回答)

I know generally two ways to design a generic linked list datastructure in C. And I'm wondering which is better. Before asking the question, I'll introduce both methods shortly:

One method is to build functions around a structure like the following:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
    void *data;
};

Obviously, the data pointer points to the payload. The list element struct is outside the payload. This is e.g. how glib has designed its double linked list facility: http://library.gnome.org/devel/glib/2.26/glib-Doubly-Linked-Lists.html

Another method is the way how it's done in the Linux kernel: http://isis.poly.edu/kulesh/stuff/src/klist/. There is no void pointer to the payload in the list element struct. Instead the list element struct is included in the payload struct:

struct list_element {
    struct list_element *prev;
    struct list_element *next;
};

struct person {
    char name[20];
    unsigned int age;
    struct list_element list_entry;
};

A special macro is used to get a pointer to the payload struct given a pointer to the list_entry, its name withing the payload struct and the type of the payload struct (the list_entry() macro).

Finally, here is the question: What is the advantage of the latter of the two methods of constructing a linked list? A few times I've heard people say the second is more 'generic' than the first, but why? I would even argue that the first method is more generic because the payload structures are list implementation agnostic, which isn't the case with the second method.
One more downside of the second method is if you want to place the payload in more than one list, you should a struct list_element member for each list in the payload structure.

Edit:
To summarize so far I saw two answers which were important for me:

  • With the first method: removing a payload from the list involves looping through the complete list until the list element pointing to the payload is found. You don't need to do this with the second method. (Answer from Patrick)
  • With the first method you have to do two malloc()s for each element: one for the payload and one for the list element struct. With the second method one malloc() is sufficient. (Answer from Roddy)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

辞旧 2024-10-12 13:11:19

我认为这是一个非常主观的问题,因为没有给出比较两者的标准。

对于简单的列表,我倾向于使用两者的组合。

struct list_node {
    struct list_node *  prev;
    struct list_node *  next;
};

struct some_struct {
    struct list_node  node;
    ...
};

尽管这看起来与第二个几乎相同,但请注意链表节点是“some_struct”的第一个元素。这意味着当您前进到下一个节点或倒回到列表中的上一个节点时,指针位于结构的开头。否则我将被迫执行一些指针数学运算才能到达“some_struct”的开头。按照目前的情况,我可以简单地进行投射。

然而,这种方法确实有其局限性。例如,如果我想要一个具有多个链表的结构,则列出的每个方法都存在缺陷,因为它需要指针算术才能到达至少一个结构的开头。为了解决这个问题,一些实现(例如 BSD VFS 代码中的实现)使用宏来创建链接列表元素。在这些中,链表始终指向结构的开头,但宏包含根据需要自动应用结构内节点偏移量的代码(用于前进到下一个,或倒回上一个)。

希望这有帮助。

编辑:修正了一些术语。

This, I think is a highly subjective question for no criteria is given against which to compare the two.

For simple lists, I tend to use a combination of the two.

struct list_node {
    struct list_node *  prev;
    struct list_node *  next;
};

struct some_struct {
    struct list_node  node;
    ...
};

Although this looks nearly identical to your second, note that the linked list node is the first element of "some_struct". This means that when you advance to the next, or rewind to the previous node in the list, the pointer is at the start of the structure. Otherwise I would be forced to perform some pointer math to get to the start of "some_struct". As it currently is, I can simply cast.

However, such a method does have its limitations. For example, if I wanted a structure with more than one linked list, each of the listed methods suffers from the flaw in that it requires pointer arithmetic to get to the start of at least one of the structures. To get around this, some implementations (such as those in BSD VFS code) use macros to create the linked list elements. In these, the linked list always points to the start of the structure, but the macro contains code to automatically apply the offset of the node within the structure if you desire it (for advancing to the next, or rewinding the previous).

Hope this helps.

Edit: Fixed some terminology.

演出会有结束 2024-10-12 13:11:18

这是课程的马。

第一种方法效率较低,因为它通常需要每个列表元素两个 malloc()free(),以及一个额外的指针间接访问它们 - 并且当然是指针的存储空间。

但是,它允许不同的列表元素具有不同大小的有效负载,这对于第二种方法可能会更尴尬。

对于第二种方法,我将重新排序结构,使列表元素位于开头 - 这确实为不同的有效负载大小提供了一定的灵活性。

struct person {
    struct list_element list_entry;
    unsigned int age;
    char name[20];  // now could be variable length.
};

It's horses for courses.

The first method is less efficient as it typically requires two malloc()s and free()s for each list element, and an additional pointer indirection to access them - and of course storage space for the pointer.

But, it allows different list elements to have different size payloads, which is potentially more awkward with the second approach.

For the second approach I would reorder the struct so the list element is at the start - this does then give some flexibility with different payload sizes.

struct person {
    struct list_element list_entry;
    unsigned int age;
    char name[20];  // now could be variable length.
};
绝對不後悔。 2024-10-12 13:11:18

第一种方法可能看起来侵入性较小,但在许多情况下并非如此(除非您添加其他数据结构)。

想象一下,您有一个一千人的列表,并且您想从列表中删除其中一个人。如果此人不知道其在列表中的位置,则您必须先扫描整个列表才能找到此人的确切位置。

您可以通过添加从 person 到其相应列表结构的指针来解决此问题,但这会破坏解决方案的非侵入性(这个词存在吗?)。

另一种替代方案是使用哈希映射将人员的内存地址映射到列表节点的内存地址。那么在列表中查找节点要快得多(但仍然比侵入式方式慢)。但是,由于这会占用更多内存,因此我建议不要这样做。

因此,最简单、最简单的解决方案就是第二种。

The first approach might seem less intrusive but in many cases it isn't (unless you add additional data structures).

Imagine you have a list of thousand persons and you want to remove one of them from the list. If the person doesn't know where it is in the list, you will have to scan the whole list first to get the exact place of the person.

You can solve this by adding a pointer from the person to its corresponding list structure, but this defeats the non-intrusiveness (does this word exist?) of the solution.

Another alternative is to have a hash map that maps the memory addresses of the persons to the memory addresses of the list nodes. Then finding the node in the list is much faster (but still slower than the intrusive way). However, since this will take a even more memory, I suggest not to do this.

Therefore, the easiest and simplest solution is the second one.

桃酥萝莉 2024-10-12 13:11:18

第一个更好,因为您可以拥有根本没有数据的列表节点。

使用第二个选项时,无论实际使用情况如何,您始终使用空格(例如,名称的 20 个字符)。

The first is better because you can have list nodes with no data at all.

With the second option you always use the space (e.g. the 20 chars for the names) regardless of actual usage.

梦里南柯 2024-10-12 13:11:18

第二种方法是侵入式列表。您必须修改要存储在列表中的结构。由于间接性较少,因此使用此方法可以获得一些性能。如果您需要更灵活的解决方案而不是最后一点性能,则应该使用第一种方法。

The second approach is an intrusive list. You have to modify the structure you want to store in the list. You gain a little bit of performance with this approach because of less indirection. If you need a more flexible solution and not the last bit of performance, you should use the first approach.

七七 2024-10-12 13:11:18

第二种方法是“侵入式”的;它需要对列表中的类型进行修改。列表(或多个列表)上的类型必须知道它在列表中。您必须能够修改结构才能将其放入列表中。

第一种方法不具有侵入性。它不需要修改结构。您可以将任何类型放入列表中。您甚至可以在单个列表中包含异构类型,尽管这会充满问题。但是,即使您无法修改基本类型,您也可以将其放在列表的第一种类型中。与此相反,它需要更多的空间。

因此,如果您可以完全控制要放入列表中的数据类型(并且可以修改它以支持您需要的列表),则第二种类型比第一种类型具有一些优势。在 Linux 内核的上下文中,前提条件已满足并且有意义。否则,第一种类型更灵活,但开销稍大。

The second method is 'intrusive'; it requires a modification to the type that is put on the list. The type on the list (or lists) has to know that it is on the list. You have to be able to modify the structure to put it on the lists.

The first method is not intrusive. It does not require modifications to the structure. You can put any type on the list. You could even have heterogeneous types on a single list, though that would be fraught with problems. However, even if the base type is not modifiable by you, you can place it on the first type of list. Against that, it requires more space.

So, if you have full control over the type of the data to be put on the list (and can modify it to support the lists you need), the second type has some advantages over the first. In the context of the Linux kernel, the preconditions are met and it makes sense. Otherwise, the first type is more flexible, but has slightly more overhead.

风向决定发型 2024-10-12 13:11:18

我认为这更多是一个概念/分析问题。您正在使用的实体是否具有列表,或者您是否有实例列表?

换句话说,如果您在数据中管理的内容具有独立的存在,则第一个是有意义的,因为无论数据点如何,都将被独立操作。如果数据始终且必然是列表的一部分,那么第二种方法可能更清晰。

与大多数设计决策一样,最重要的标准应该是更清晰、最明显。

I think it's more of a conceptual/analysis issue. Is the entity you're working with something that has lists or do you have a list of instances?

In other words, if what you're managing in the data has an independent existence, the first makes sense since whatever data points at will be manipulated independently. If the data is always and necessarily part of the list, then the second approach may be clearer.

As in most design decisions, the most important criteria should be which is clearer and most obvious.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文