list_head: 如果从第二个元素开始解析则得到垃圾

list_head: get garbage if start parsing from second element

我在我的 C 项目中使用 list_head 结构来定义 linked_list。在某些情况下,我需要从第二个元素解析列表,但在这种情况下,我得到了一个带有垃圾值的附加元素。我尝试在我的电脑中使用一个小程序来模拟相同的场景。我遇到了同样的问题:

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

struct struct_report{
        struct list_head list;
        char *report;
};


//Add an element to the linked list
void add_report_to_list(struct list_head *reports, char *report) {
        struct struct_report *report_strct;
        report_strct = calloc(1, sizeof(struct struct_report));
        list_add_tail(&report_strct->list, reports);
        report_strct->report= strdup(report);
}

int main() {
        struct struct_report *retreport;
        LIST_HEAD(reports); //instantiate a struct list_head instance
        add_report_to_list(&reports, "elt1");
        add_report_to_list(&reports, "elt2");
        add_report_to_list(&reports, "elt3");
        add_report_to_list(&reports, "elt4");
        list_for_each_entry(retreport, &reports, list){
                printf("============> no next retreport: %s\n", retreport->report);
        }
        printf("\n");
        list_for_each_entry(retreport, reports.next, list){
                printf("============> Next retreport: %s\n", retreport->report);
        }
        return 1;
} 

list.h 与 linux 相同:https://github.com/torvalds/linux/blob/master/include/linux/list.h

我得到以下跟踪结果作为执行结果:

============> no next retreport: elt1
============> no next retreport: elt2
============> no next retreport: elt3
============> no next retreport: elt4

============> Next retreport: elt2
============> Next retreport: elt3
============> Next retreport: elt4
============> Next retreport: 

很明显,在我从第一个元素开始正常解析的情况下,我没有任何问题。但是在我从列表中的第二个元素开始的情况下,我得到了另一个具有奇怪值的垃圾。

有一些解释为什么我得到一个额外的元素吗?以及如何修复它以解析直到 elt4?

如果您从列表的第一个元素开始(而不是从头开始),那么 list_for_each_entry() 将在同一个列表对象中停止,因为它是一个循环列表。

所以 list_for_each_entry() 将通过头部。并且头部没有附加到条目上。因此,当您尝试引用头列表中的条目时,您将得到垃圾

解决方法:从链表头部开始循环,跳过第一个元素

列表实现实际上创建了一个环。列表头是一个虚拟元素,它将 next 指向第一个元素,将 prev 指向最后一个元素。 (最初两者都指向列表头本身。)在尾部添加一个元素实际上实现为添加它"before the list head"。在这个环上循环时,头部由指向它的单独指针标记。没有其他方法可以将它与列表的其他元素区分开来。

list_for_each_entry中的for循环以head指针作为循环条件进行比较,因此当它再次到达作为列表头提供的对象时将停止.

/**
 * list_for_each_entry  -   iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:   the head for your list.
 * @member: the name of the list_head within the struct.
 */
#define list_for_each_entry(pos, head, member)              \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

list_first_entrylist_next_entry return 指向用户定义的结构的指针,该结构应该通过使用宏 container_of 包含 struct list_head .

如果您将 reports.next 而不是 &reports 传递给 list_for_each_entry(),它将把它作为虚拟列表头元素,并将环中的所有其他元素视为真正的列表条目。

您的代码为尾部元素后面的元素打印垃圾,因为这是一个纯 struct list_head,未嵌入 struct struct_report,因此宏 list_next_entry return 是指向 main()struct list_head reports 之前的内存的指针,这是未定义的行为。

如果你的程序没有崩溃,你会在 elt4 之后得到同样的垃圾,如果你通过例如reports.next->next。在这种情况下,我希望输出如下:

============> Next retreport: elt3
============> Next retreport: elt4
============> Next retreport: <garbage>
============> Next retreport: elt1

虽然同一类型 - list_head - 用于两者:

  • 列表头,
  • 列表元素,

它们不可互换。如果某些宏需要 list head 作为参数,您需要提供 exactly 指向 list head 的指针,而不是指向 列表元素 .

的指针

list_for_each_entry 接受指向 列表头 的指针作为第二个参数,因此不应传递指向元素的指针。

用于在遍历列表时跳过第一个元素,宏

  • list_for_each_entry_from
  • list_for_each_entry_continue

可以用。

这两个宏都采用与list_for_each_entry相同的参数,但它们考虑了游标的初始值(第一个参数):

  • list_for_each_entry_from 开始迭代 光标指向的元素,
  • list_for_each_entry_continue 开始迭代 光标指向的元素之后。

因此,跳过第一个元素遍历列表可以如下:

// Set cursor to the first element in the list
retreport = list_first_entry(reports, typeof(*retreport), list);
// Iterate starting after the cursor
list_for_each_entry_continue(retreport, reports, list){
    printf("============> Next retreport: %s\n", retreport->report);
}