Linux内核列表实现会导致UB吗?

Phi*_*ipp 5 c linked-list list linux-kernel

先决条件:

  1. 根据C 标准,指针算术会产生无效指针,从而导致未定义的行为。
  2. Linux 源代码似乎符合C 标准,希望与大多数体系结构兼容。
  3. Linux的列表实现包含以下代码(保留格式,可能另一个问题的想法是如何使用Stackoverflow语法设置正确的制表宽度):
#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))
Run Code Online (Sandbox Code Playgroud)
  1. 上述列表实现的典型用例是具有 type 的结构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();
}
Run Code Online (Sandbox Code Playgroud)

然后最后(循环退出之前)的评估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();
}
Run Code Online (Sandbox Code Playgroud)

,根据我们的假设,它将指向 A 结构之前的区域。假设该区域不包含已分配的内存,则container_of()宏的结果将是无效指针,从而导致 Linux 中的 UB(一般情况下为 OFC)。这个推理合理还是我错了?

或者该标准的某些部分是否被普遍认为不值得遵循?

Ian*_*ott 3

正如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 *),但这通常需要在代码中添加额外的变量。