问题 通过空指针获取成员变量的地址是否会产生未定义的行为?


以下代码(或其等效使用null文字的显式强制转换来摆脱临时变量)通常用于计算类或结构中特定成员变量的偏移量:

class Class {
public:
    int first;
    int second;
};

Class* ptr = 0;
size_t offset = reinterpret_cast<char*>(&ptr->second) -
                 reinterpret_cast<char*>(ptr);

&ptr->second 看起来它等同于以下内容:

&(ptr->second)

而这相当于

&((*ptr).second)

它取消引用对象实例指针并为空指针产生未定义的行为。

那么原始罚款还是会产生UB?


11775
2017-09-08 13:23


起源

offset 如在 offsetof 应该是一个 size_t 你有,但两个指针的差异应该是 ptrdiff_t 所以这里有点不对劲 - Paul Evans
你想要实施吗? offsetof?看看它的“可能的实施”,你的问题的答案是 没有,它不是UB(如果类是标准布局类型;也在链接页面中提到)。 - leemes
@leemes cppreference不是标准。 - Yakk - Adam Nevraumont
@leems“可能的实现” offsetof 关于cppreference是 不 强有力的证据表明取消引用null不是UB。它可能是观察性的 - 有编译器 offsetof 实现完全一样。编译器提供的事实 offsetof 实现方式并没有说明行为是否是C ++中的UB:它强烈暗示它是被定义的 在那特定的编译器中 (或编译器在其实现中存在错误 offsetof)。编译器可以自己定义UB:它们的头部不是找到保证定义的行为C ++的地方。 - Yakk - Adam Nevraumont
“是否取消引用空指针而不导致左值到右值转换是UB”的问题是 CWG问题232,仍然是开放的。 - T.C.


答案:


尽管它什么也没做, char* foo = 0; *foo;    可能 未定义的行为。

取消引用空指针   可能 未定义的行为。是的 , ptr->foo 相当于 (*ptr).foo,和 *ptr 取消引用空指针。

目前有一个 公开问题 关于if的工作组 *(char*)0 如果您不读取或写入,则为未定义的行为。标准的一部分暗示它是,其他部分暗示它不是。目前的笔记似乎倾向于定义它。

现在,这是理论上的。在实践中怎么样?

在大多数编译器中,这是有效的,因为在解除引用时没有进行检查:空指针所指向的内存被保护以防止访问,并且上面的表达式只是取一些null的地址,它不会读取或写入那里的值。

这就是为什么 cpp参考 offsetof 作为可能的实现列出了相当多的技巧。事实上,有些人(很多人?大多数人?我检查过的每一个人?)编译器实现了这一事实 offsetof 以类似或等效的方式,并不意味着行为在C ++标准下得到了很好的定义。

但是,考虑到模糊性,编译器可以自由地在每个取消引用指针的指令上添加检查,并执行任意代码(例如,如果确实取消引用了null,则无法快速报告错误)。这样的检测甚至可能有助于发现它们发生的错误,而不是发生症状的位置。并且在附近有可写存储器的系统上 0 这样的仪器可能是关键的(OSX之前的MacOS有一些可写的内存可以控制系统功能 0)。

这样的编译器仍然可以编写 offsetof 那样,并介绍 pragma用于阻止生成的代码中的检测。或者他们可以切换到内在的。

更进一步,C ++在如何安排非标准布局数据方面留下了很多自由。理论上,类可以实现为相当复杂的数据结构,而不是我们期望的近乎标准的布局结构,并且代码仍然是有效的C ++。访问成员变量到非标准布局类型并获取其地址可能会有问题:我不知道是否可以保证非标准布局类型中成员变量的偏移量不会在实例之间发生变化!

最后,一些编译器具有积极的优化设置,可以找到执行未定义行为的代码(至少在某些分支或条件下),并使用它来将该分支标记为无法访问。如果确定null解除引用是未定义的行为,则这可能是一个问题。一个典型的例子是gcc的积极签名整数溢出分支消除器。如果标准规定某些内容是未定义的行为,则编译器可以自由地考虑该分支无法访问。如果null取消引用不在函数中的分支后面,则编译器可以自由地声明调用该函数的所有代码都无法访问,并递归。

并且可以自由地在当前的,但是下一版本的编译器中执行此操作。

编写标准有效的代码不仅仅是编写今天编译干净的代码。虽然定义解除引用和不使用空指针的程度目前是模糊的,但依赖于仅模糊定义的内容是有风险的。


9
2017-09-08 14:02