70

比较悬空指针是否合法?

int *p, *q;
{
    int a;
    p = &a;
}
{
    int b;
    q = &b;
}
std::cout << (p == q) << '\n';

请注意两者如何pq指向已经消失的对象。这合法吗?

4

3 回答 3

57

简介:第一个问题是,使用值是否合法p

a销毁后,p获取所谓的无效指针值。引自N4430(有关 N4430 状态的讨论,请参见下面的“注释”):

当存储区域的持续时间结束时,表示已释放存储的任何部分地址的所有指针的值都变为无效指针值

N4430 的同一部分也介绍了使用无效指针值时的行为(C++14 [basic.stc.dynamic.deallocation]/4 中出现了几乎相同的文本):

通过无效指针值的间接传递以及将无效指针值传递给释放函数具有未定义的行为。无效指针值的任何其他使用都具有实现定义的行为

[脚注:某些实现可能会定义复制无效指针值会导致系统生成的运行时错误。——结束脚注]

因此,您需要查阅实现的文档以了解此处应该发生的情况(自 C++14 起)。

上述引用中的术语use意味着需要进行左值到右值的转换,如 C++14 [conv.lval/2]:

当左值到右值的转换应用于表达式 e,并且 [...] 泛左值引用的对象包含无效的指针值时,行为是实现定义的。


历史:在 C++11 中,这表示undefined而不是implementation-defined;它被DR1438改变了。查看这篇文章的编辑历史以获取完整的报价。


适用于p == q假设我们在 C++14+N4430 中接受了评估的结果,p并且q是实现定义的,并且实现没有定义发生硬件陷阱;[expr.eq]/2 说:

如果两个指针都为空、都指向同一个函数或都表示相同的地址(3.9.2),则它们比较相等,否则它们比较不相等。

由于它是由实现定义的,何时获取pq评估什么值,我们不能确定这里会发生什么。但它必须是实现定义的或未指定的。

在这种情况下,g++ 似乎表现出未指定的行为;取决于-O开关,我可以让它说1or ,对应于相同的内存地址在被破坏后是否0被重新使用。ba


关于 N4430 的注意事项:这是针对 C++14 提出的缺陷解决方案,尚未被接受。它清理了很多围绕对象生命周期、无效指针、子对象、联合和数组边界访问的措辞。

在 C++14 文本中,在 [basic.stc.dynamic.deallocation]/4 和后续段落中定义了使用时会出现无效指针值delete。但是,没有明确说明相同的原则是否适用于静态或自动存储。

[basic.compound]/3 中有一个“有效指针”的定义,但它过于模糊,无法合理使用。[basic.life]/5(脚注)指的是相同的文本来定义指向对象的指针的行为静态存储持续时间,这表明它适用于所有类型的存储。

在 N4430 中,文本从该部分上移一级,以便清楚地适用于所有存储持续时间。有附注:

起草说明:这应该适用于所有可以结束的存储期限,而不仅仅是动态存储期限。在支持线程或分段堆栈的实现上,线程和自动存储的行为方式可能与动态存储相同。


我的观点:p除了说获取无效指针值之外,我看不到任何一致的方式来解释标准(N4430 之前) 。除了我们已经看过的内容之外,任何其他部分似乎都没有涵盖该行为。因此,我很高兴将 N4430 措辞视为在这种情况下代表标准的意图。


于 2015-06-07T13:28:47.940 回答
4

从历史上看,有些系统使用指针作为右值可能会导致系统获取由该指针中的某些位标识的某些信息。例如,如果一个指针可以包含对象头的地址以及对象的偏移量,那么获取指针可能会导致系统也从该头获取一些信息。如果对象已不复存在,则从其标头获取信息的尝试可能会失败并产生任意后果。

话虽如此,在绝大多数 C 实现中,在某个特定时间处于活动状态的所有指针将永远与关系和减法运算符保持相同的关系,就像它们在那个特定时间一样。实际上,在大多数实现中,如果具有,则可以通过检查是否char *p来确定它是否标识对象的一部分;如果对象的生命周期中有任何重叠,这种比较甚至可以追溯。char *base; size_t size;(size_t)(p-base) < size

不幸的是,该标准没有定义代码可以表明它需要任何后者保证的方法,也没有标准方法可以让代码询问特定实现是否可以承诺任何后者的行为,如果不能,则拒绝编译. 此外,一些超现代的实现会将在两个指针上使用关系或减法运算符视为程序员的承诺,即所讨论的指针将始终标识相同的活动对象,并省略任何仅在该假设下才相关的代码没有举行。因此,即使许多硬件平台能够提供对许多算法有用的保证,也有'

于 2015-06-08T16:05:59.580 回答
-3

指针包含它们引用的变量的地址。即使过去存储在那里的变量被释放/销毁/不可用,这些地址也是有效的。只要您不尝试使用这些地址的值,您就是安全的,这意味着 *p 和 *q 将是未定义的。

显然结果是实现定义的,因此如果不想深入研究汇编代码,可以使用此代码示例来研究编译器的功能。

这是否是一种有意义的做法是完全不同的讨论。

于 2015-06-10T14:46:54.317 回答