通过空指针获取成员变量的地址是否会产生未定义的行为
Does taking address of member variable through a null pointer yield 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);
&ptr->second
看起来相当于以下内容:
&(ptr->second)
这又相当于
&((*ptr).second)
其解引用对象实例指针并产生空指针的未定义行为。
那么,原来是罚款还是产生UB?
尽管它什么都不做,但char* foo = 0; *foo;
是可能是未定义的行为。
取消引用空指针是可能是未定义的行为。是的,ptr->foo
等价于(*ptr).foo
,*ptr
取消引用空指针。
目前,工作组中存在一个悬而未决的问题,即如果你不读或写*(char*)0
,它是否是未定义的行为。标准的部分内容意味着它是,其他部分则意味着它不是。目前那里的注释似乎倾向于定义它。
现在,这是理论上的。练习怎么样?
在大多数编译器中,这是因为在去引用时不进行检查:null指针指向的内存被保护不受访问,而上面的表达式只是获取null周围某个东西的地址,它不会读取或写入那里的值。
这就是为什么cpp引用offsetof
列出了很多可能的实现技巧。事实上,一些(我检查过的大多数编译器)编译器以类似或等效的方式实现offsetof
,这并不意味着行为在C++标准下得到了很好的定义。
然而,考虑到歧义性,编译器可以自由地在每个取消引用指针的指令上添加检查,如果确实取消了对null的引用,则可以执行任意代码(例如,故障快速错误报告)。这样的工具甚至可能有助于找到错误发生的地方,而不是症状发生的地方。在0
附近有可写存储器的系统上,这种仪器可能是关键(OSX之前的MacOS有一些可写存储器,可以控制0
附近的系统功能)。
这样的编译器仍然可以用这种方式编写offsetof
,并引入pragma
s等来阻止生成的代码中的插入。或者他们可以转换为内在的。
更进一步,C++在如何排列非标准布局数据方面留下了很大的自由度。理论上,类可以实现为相当复杂的数据结构,而不是我们所期望的几乎标准的布局结构,并且代码仍然是有效的C++。访问非标准布局类型的成员变量并获取它们的地址可能会有问题:我不知道是否可以保证非标准布局中成员变量的偏移量不会在实例之间发生变化!
最后,一些编译器有积极的优化设置,可以找到执行未定义行为的代码(至少在某些分支或条件下),并使用这些代码将该分支标记为不可访问。如果确定null取消引用是未定义的行为,这可能是一个问题。一个经典的例子是gcc的激进有符号整数溢出分支消除器。如果标准规定某个东西是未定义的行为,编译器可以自由地认为该分支是不可访问的。如果null解引用不在函数的分支后面,编译器可以自由地声明调用该函数的所有代码都是不可访问的,并递归。
在当前版本中,而是在下一版本的编译器中,这样做是免费的。
编写标准有效的代码不仅仅是编写今天编译干净的代码。虽然目前定义的去引用和不使用空指针的程度不明确,但依赖定义不明确的东西是有风险的。
- 不知道某个东西是否被忽略会引入未定义的行为吗
- 此增量后语句是否会导致未定义的行为?
- Windows 链接器是否使用 LoadLibrary 解析 DLL 中未定义的符号?
- 如何测试 size_t -1 是否未定义,其中 size_t 为 0?
- 在销毁期间从另一个线程调用对象上调用方法是否未定义行为?
- 从 std::string 到 std::array<char,size> 的 memcopy 额外数据是否是一种未定义的行为?
- 负指数是否必然意味着未定义的行为
- 在"printf"中使用标签"h"或"hh"是否涉及未定义的
- 在 C++17 中,是否未定义使用无锁原子学保护从信号处理程序传递的数据?
- C++ 如何检查 char 变量是否未定义(未初始化)
- 递减 std::vector::begin 是否未定义,即使它从未被使用过?
- 访问从联合与另一个成员集复制的联合中的一个成员是否未定义或未指定?
- 在C++中,转换为simd类型是否有未定义的行为
- 是否是从等待返回到悬而未决的"this"实例的未定义行为?
- 是否未定义将对函数范围变量的引用作为值返回
- FBString 的小字符串优化是否依赖于未定义的行为?
- 移动 std::bitset<N> 是否超过 N 个位置未定义的行为?
- 创建指针是否超过非数组指针的末尾,而不是从 C++17 中的一元运算符和未定义的行为派生?
- 使用c_str是否有未定义的异常行为
- i=i++;未定义.是否i=foo(i++)也未定义