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

sha*_*oth 12 c++ offset undefined-behavior

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

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

Class* ptr = 0;
size_t offset = reinterpret_cast<char*>(&ptr->second) -
                 reinterpret_cast<char*>(ptr);
Run Code Online (Sandbox Code Playgroud)

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

&(ptr->second)
Run Code Online (Sandbox Code Playgroud)

而这相当于

&((*ptr).second)
Run Code Online (Sandbox Code Playgroud)

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

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

Yak*_*ont 10

尽管它什么也不做的其实char* foo = 0; *foo; 可以不确定的行为.

取消对NULL指针 可能是不确定的行为.是的,ptr->foo相当于(*ptr).foo,并*ptr取消引用空指针.

工作组中目前存在一个未解决的问题,即*(char*)0如果您不读取或写入,则该问题是未定义的行为.标准的一部分暗示它是,其他部分暗示它不是.目前的笔记似乎倾向于定义它.

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

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

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

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

这样的编译器仍然可以这样编写offsetof,并引入pragmas等来阻止生成的代码中的检测.或者他们可以切换到内在的.

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

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

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

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