比较悬空指针是否合法

Is it legal to compare dangling pointers?

本文关键字:是否 空指针 比较      更新时间:2023-10-16

比较悬空指针是否合法?

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

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

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

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

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

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

通过无效指针值

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

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

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

上述引文中的术语使用意味着需要将左值转换为右值,如 C++14 [conv.lval/2] 所示:

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


历史:在C++11中,这表示未定义而不是实现定义;DR1438对其进行了更改。 有关完整报价,请参阅本文的编辑历史记录。


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

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

由于它是实现定义的,因此在评估pq时会获得什么值,因此我们无法确定这里会发生什么。但它必须是实现定义的或未指定的。

在这种情况下,g++似乎表现出未指定的行为;根据-O开关,我能够让它说10,对应于相同的内存地址是否在a被销毁后重新用于b


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

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

在 [basic.compound]/3 中有一个定义"有效指针",但它太模糊而无法合理使用。[basic.life]/5(脚注(引用了相同的文本,以定义指向静态存储持续时间对象的指针的行为,这表明它旨在应用于所有类型的存储。

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

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


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


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

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

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

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

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

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