【问题标题】:Does this implementation of offsetof invoke undefined behavior? [duplicate]这个 offsetof 的实现会调用未定义的行为吗? [复制]
【发布时间】:2019-12-12 00:08:47
【问题描述】:

offsetofstddef.h 中是这样定义的:

#define offsetof(type, member) ((size_t)&((type *)0)->member)

由于 NULL 指针的取消引用,这是否会调用未定义的行为?如果不是,为什么?

【问题讨论】:

  • 没有空指针的解引用
  • 它前面有&。这很重要。
  • <stddef.h> 的内容是 C 实现的一部分,而不是程序的一部分。询问它是否未定义就像询问某些恰好是编译器源代码一部分的汇编语言是否具有 C 标准未定义的行为——当然它具有未定义的行为,因为标准未涵盖它。通常<stddef.h> 是与编译器一起设计的。除非您的情况是您尝试使用您无法控制且只能依赖标准指定内容的编译器来实现您自己的 <stddef.h>,否则问题就错了。
  • @JL2210 #define offsetof(type, f) ((size_t) \ ((char *)&((type *)0)->f - (char *)(type *)0)) 版本(带有减法,以免,上帝保佑,空指针常量并非所有位为零)应该是非常可移植的。你需要一个额外的智能编译器来搞砸它。

标签: c undefined-behavior offsetof


【解决方案1】:

在普通的 C 代码中,((size_t)&((type *)0)->member) 的行为不是 C 标准规定的:

  • 首先,根据C 2018 6.5.2.3 4,关于->((type *)0)->member指定(type *)0指向的结构的成员member的左值。但是((type *)0) 不指向结构,因此没有成员可以作为左值。
  • 假设它确实为某些假设结构提供了左值,则无法保证获取其地址并将其转换为 size_t 会产生成员的偏移量,因为我们不知道 (type *)0 产生的地址实际上在实现的寻址方案中用零表示,并且因为将指针转换为 C 2018 6.3.2.3 6 指定的整数只告诉我们结果是实现定义的,而不是它以任何其他有意义的形式产生地址。

如果此代码在标准标头中,例如<stddef.h>,它受 C 实现而不是 C 标准的控制,因此根据 C 标准是否未定义的问题不适用。 C 标准仅说明标准头文件在包含时的行为方式——实现可以使用它选择的任何方式来实现所需的效果,无论是简单地定义 C 标准未完全定义的源代码的行为,还是将源代码放入在标题中使用完全不同的语言。 (事实上​​,文件 stddef.h 可能完全为空或根本不存在,编译器可以在看到#include <stddef.h> 时提供所需的声明,而无需从磁盘读取任何实际文件。)

【讨论】:

  • 当在<stddef.h> 中时,“它在 C 实现的控制之下”所以没有 UB - 是的。
  • Nitpick:此代码片段中没有发生指针到整数的转换。 (type *)0 生成类型为 type 的 NULL 指针。将-> 应用于空指针是一种取消引用,它具有未定义的行为。 6.5.3.2 中&*ptr&ptr[i] 的特殊情况不适用,因此& 的存在无关紧要。
  • @zwol 有趣的是,port70.net/~nsz/c/c11/n1570.html#6.6p9 提到 -> 和 & 可以用于创建 地址常量,前提是没有访问任何对象。这意味着 -> 在这种情况下可能不是真正的取消引用(=对象访问?)。
  • @PSkocik 不,如果委员会的意思是 ->& 下方(在表达式树中)不被取消引用,他们会在 6.5.3.2 中这样说,因为他们为&*ptr&ptr[i] 做过。 (有一个有趣的论点认为这是一个疏忽,但应该有人提交 DR。)
  • @zwol 鉴于member 的类型为int,并且位于结构中的偏移量x,难道&ptr->member 不等于&*(int *)((char *)ptr + x)
【解决方案2】:

撇开所有其他原因,它可能不是offsetof 的正确实现,

#define offsetof(type, member) ((size_t)&((type *)0)->member)

不合适即使作为实现的一部分,因为stddef.h 中的所有内容都必须在 C 和 C++ 中都能正常工作,而在 C++ 中,上述构造在存在重载 @ 时肯定会出现异常987654324@。这就是为什么 GCC 的 stddef.h 在十五年前转而使用称为 __builtin_offsetof 的特殊内在函数的原因。

是的,我的意思是,如果您在某些 stddef.h 中看到了这一点,那么 stddef.h 就是错误的。

【讨论】:

  • 嗯,具体来说,我看到了这个:#if __GNUC__ > 3 #define offsetof(type, member) __builtin_offsetof(type, member) #else #define offsetof(type, member) ((size_t)&((type *)0)->member) #endif(引用错误的版本)
  • 仍然有问题。该结构有点不同,但它具有您最初引用的所有问题。如果没有专用的编译器内在函数,这根本无法在 C++ 中完成。无论是哪种实现,都应该在其后备情况下使用#error
  • 没有强制要求 C++ 编译器能够使用 C 编译器提供的头文件;唯一的要求是C++编译器提供的<stddef.h>必须是C++编译器可以接受的(当然C编译器提供的<stddef.h>也必须是C编译器可以接受的)。
  • 从什么时候开始,语言律师的问题会推迟到现实或 C++,@JL2210?尤其是“仅限 C 语言”的语言律师问题。
  • @JL2210:即使在实践中,成对的 C 实现和 C++ 实现使用一个名为 stddef.h 的公共文件来实现 <stddef.h>,但确实存在不成对的 C 实现使用 C++ 实现,因此在这个答案中断言 stddef.h 中的代码必须在 C 和 C++ 中都能正常工作是错误的。
猜你喜欢
  • 2010-10-17
  • 2021-11-15
  • 2014-08-04
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多