以下代码(或其等效使用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?
尽管它什么也没做, 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取消引用不在函数中的分支后面,则编译器可以自由地声明调用该函数的所有代码都无法访问,并递归。
并且可以自由地在当前的,但是下一版本的编译器中执行此操作。
编写标准有效的代码不仅仅是编写今天编译干净的代码。虽然定义解除引用和不使用空指针的程度目前是模糊的,但依赖于仅模糊定义的内容是有风险的。