我在我的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: %sn", retreport->report);
}
printf("n");
list_for_each_entry(retreport, reports.next, list){
printf("============> Next retreport: %sn", 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()
会穿过头部。并且头部未连接到入口。因此,当您尝试引用 head 列表中的条目时,您将获得垃圾
解决方案:从列表的头部开始循环并跳过第一个元素
列表实现实际上创建了一个环。列表头是一个虚拟元素,它next
指向第一个元素,prev
指向最后一个元素。(最初两者都指向列表头本身。 在尾部添加一个元素实际上被实现为"在列表头之前"添加它。当环过这个环时,头部由指向它的单独指针标记。没有其他方法可以将其与列表中的其他元素区分开来。
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_entry
和list_next_entry
都返回指向用户定义结构的指针,该结构应通过使用宏container_of
包含struct list_head
。
如果您传递reports.next
而不是&reports
list_for_each_entry()
,它将将其作为虚拟列表头元素,并将环中的所有其他元素视为真正的列表条目。
你的代码为tail元素后面的元素打印垃圾,因为这是一个没有嵌入struct struct_report
的纯struct list_head
,所以宏list_next_entry
在main()
struct list_head reports
之前返回一个指向内存的指针,这是未定义的行为。
如果你的程序没有崩溃,你会在elt4
后得到同样的垃圾,如果你会通过,例如reports.next->next
.在这种情况下,我希望输出如下:
============> Next retreport: elt3
============> Next retreport: elt4
============> Next retreport: <garbage>
============> Next retreport: elt1
虽然相同的类型 -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: %sn", retreport->report);
}