【发布时间】:2021-02-27 17:46:30
【问题描述】:
先决条件:
- 根据C standard,会产生无效指针的指针算术会导致未定义的行为。
- Linux 源代码seems to conform 符合 C 标准,希望与大多数架构兼容。
- Linux's list implementation 包含以下代码(保留格式,可能另一个问题的想法是如何使用 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))
- 上述列表实现的典型用例具有类型为
struct A的结构,其中包含struct B类型结构列表的头部。
Q:假设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) 的最后一次(循环退出之前)评估将扩展到:
container_of(A_ptr->list_head, struct B, entry_in_list) =
= (char*)A_ptr->list_head - offsetof(struct B, entry_in_list) =
= (char*)A_ptr + offsetof(struct A, list_head) - offsetof(struct B, entry_in_list)
,根据我们的假设,它将指向 A 结构之前的区域。假设这个区域不包含分配的内存,container_of() 宏的结果将是一个无效的指针,从而导致 Linux 中的 UB(一般情况下为 OFC)。这个推理是合理的还是我弄错了?
或者该标准的某些部分普遍认为不值得遵循?
【问题讨论】:
-
Linux内核使用了一些GCC扩展如
typeof,也对C实现做了一些假设。 -
@IanAbbott 感谢您的评论。但似乎不仅要对编译器(以及 C 实现)做出假设,而且还要对将使用该通用接口的体系结构做出假设。我相信 GCC 可能只是将此 C 代码转换为汇编,假设它不违反 C 标准,并且只有当 CPU 看到分配给与
pos关联的寄存器的无效指针时,UB 才会被披露。 -
您是正确的,
list_for_each_entry和list_entry_is_head宏在 C 标准中快速而松散。当循环终止条件预计为假时,pos变量未指向struct B对象,因此在list_entry_is_head中访问&pos->entry_in_list会在此处调用UB。 -
@IanAbbott 我相信这算是一个完整的答案:)
标签: c list linked-list linux-kernel