比较悬空指针是否合法?
int *p, *q;
{
int a;
p = &a;
}
{
int b;
q = &b;
}
std::cout << (p == q) << '\n';
注意p
和q
如何指向已经消失的对象。这合法吗?
答案 0 :(得分:57)
简介:第一个问题是,使用p
的值是否合法。
销毁a
后,p
获取所谓的无效指针值。来自N4430的报价(有关N4430状态的讨论,请参阅下面的“注释”):
当达到存储区域的持续时间结束时,表示解除分配存储的任何部分的地址的所有指针的值变为无效指针值。
使用无效指针值时的行为也在N4430的同一部分中介绍(几乎相同的文本出现在C ++ 14 [basic.stc.dynamic.deallocation] / 4中):
通过无效指针值间接并将无效指针值传递给释放函数具有未定义的行为。对无效指针值的任何其他使用都具有实现定义的行为。
[脚注:某些实现可能会定义复制无效指针值会导致系统生成的运行时故障。 - 结束脚注]
所以你需要查阅你的实现文档来了解这里应该发生什么(从C ++ 14开始)。
上述引号means中的术语使用,需要进行左值到右值的转换,如C ++ 14 [conv.lval / 2]:
当对表达式e应用左值到右值转换时,glvalue引用的对象包含无效指针值,该行为是实现定义的。
历史记录:在C ++ 11中,这表示 undefined 而不是实现定义的;它被DR1438更改了。有关完整报价,请参阅此帖子的编辑历史记录。
应用于p == q
:假设我们已在C ++ 14 + N4430中接受评估p
和q
的结果是实现定义的,并且实现没有定义硬件陷阱发生; [expr.eq] / 2说:
两个指针比较相等,如果它们都是空的,都指向相同的函数,或者两者都代表相同的地址(3.9.2),否则它们比较不相等。
由于它的实现定义了在评估p
和q
时获得的值,我们无法确定这里会发生什么。但它必须是实现定义的或未指定的。
-O
开关,我可以让它说1
或0
,对应于b
之后是否重新使用相同的内存地址a
{1}}已被摧毁。
关于N4430的注释:这是对C ++ 14的建议缺陷解决方案,尚未被接受。它清理了很多围绕对象生命周期,无效指针,子对象,联合和数组边界访问的措辞。
在C ++ 14文本中,它在[basic.stc.dynamic.deallocation] / 4和后续段落中定义,当使用delete
时出现无效指针值 。但是,没有明确说明相同的原则是否适用于静态或自动存储。
在[basic.compound] / 3中有一个定义“有效指针”,但是它太模糊了,不能理智地使用。[basic.life] / 5(脚注)引用相同的文本来定义指针的行为对于静态存储持续时间的对象,这表明它适用于所有类型的存储。
在N4430中,文本从该部分向上移动一级,以便它明确适用于所有存储持续时间。附上一张纸条:
起草说明:这应该适用于可以结束的所有存储持续时间,而不仅仅是动态存储持续时间。在支持线程或分段堆栈的实现上,线程和自动存储的行为方式与动态存储的行为方式相同。
我的观点:我没有看到任何一致的方式来解释标准(前N4430),而不是说p
获得无效的指针值。除了我们已经看过的内容之外,其他任何部分似乎都没有涵盖这种行为。所以我很高兴在这种情况下将N4430措辞视为代表标准的意图。
答案 1 :(得分:4)
从历史上看,有些系统使用指针作为右值可能会导致系统获取该指针中某些位标识的某些信息。例如,如果指针可以包含对象的标题的地址以及对象的偏移量,则获取指针可能导致系统也从该标题中获取一些信息。如果该对象已不复存在,则从其头部获取信息的尝试可能会失败并产生任意后果。
据说,在绝大多数C实现中,在某个特定时刻生存的所有指针将永远保持与关系运算符和减法运算符相同的关系,就像它们在特定时间一样。实际上,在大多数实现中,如果一个具有char *p
,则可以通过检查char *base; size_t size;
来确定它是否通过(size_t)(p-base) < size
识别对象的一部分。如果对象中存在任何重叠,这种比较甚至可以回顾性地进行。寿命。
不幸的是,标准没有定义代码可以表明它需要任何后一种保证的方法,也没有一种标准方法可以让代码可以询问特定实现是否可以保证后面的任何行为并拒绝编译它不是。此外,一些超现代的实现将在两个指针上使用关系或减法运算符作为程序员的承诺,即所讨论的指针将始终标识相同的活动对象,并省略任何仅在该假设时相关的代码没有坚持。因此,即使许多硬件平台能够提供对许多算法有用的保证,但即使代码永远不需要在没有代码的硬件上运行,代码也无法安全地利用任何此类保证。自然地提供它们。
答案 2 :(得分:-3)
指针包含它们引用的变量的地址。即使以前存储的变量被释放/销毁/不可用,地址也是有效的。 只要您不尝试使用这些地址的值,您就是安全的,这意味着* p和* q将是未定义的。
显然,结果是实现定义的,因此如果您不想深入研究汇编代码,可以使用此代码示例来研究编译器的功能。
这是否是一种有意义的做法是完全不同的讨论。