Phi*_*ipp 5 c linked-list list linux-kernel
先决条件:
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
#define list_next_entry(pos, member) \
    list_entry((pos)->member.next, typeof(*(pos)), member)
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)
#define list_entry_is_head(pos, head, member)               \
    (&pos->member == (head))
#define list_for_each_entry(pos, head, member)              \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         !list_entry_is_head(pos, head, member);            \
         pos = list_next_entry(pos, member))
struct A,其中包含 type 结构列表的头部struct B。问:我们假设offsetof(struct B, entry_in_list) > offsetof(struct A, list_head)实现了以下循环:
struct A* A_ptr = something_meaningful;
struct B* pos = NULL;
list_for_each_entry(pos, &A_ptr->list_head, entry_in_list) {
  do_something();
}
然后最后(循环退出之前)的评估list_next_entry(pos, member)将扩展到:
struct A* A_ptr = something_meaningful;
struct B* pos = NULL;
list_for_each_entry(pos, &A_ptr->list_head, entry_in_list) {
  do_something();
}
,根据我们的假设,它将指向 A 结构之前的区域。假设该区域不包含已分配的内存,则container_of()宏的结果将是无效指针,从而导致 Linux 中的 UB(一般情况下为 OFC)。这个推理合理还是我错了?
或者该标准的某些部分是否被普遍认为不值得遵循?
正如OP怀疑的那样,宏的实现list_for_each_entry(pos, head, member)取决于C语言中未定义的行为,以便循环终止条件!list_entry_is_head(pos, head, member)变为假。
假设列表非空,则在最终迭代之后,循环的第三个“前进”表达式在指向的地址字节之前for生成一个指向无效值的指针。但它仍然依赖于比较等于。typeof(*pos)offsetof(typeof(*pos), member)struct list_headhead&pos->memberhead
尽管它取决于未定义的行为,但编译器很难确定它pos在技术上是无效的指针。只要 和 都pos指向head相同的平面地址空间,Linux 内核就能够摆脱这种规则的扭曲。
另一种方法是根本#include <linux/list.h>不提供list_for_each_entry(pos, head, member)宏,而让代码使用list_for_each(pos, head)和list_entry(ptr, type, member)宏(其中pos是 a struct list_head *,并且ptr是 a type *),但这通常需要在代码中添加额外的变量。