比较悬空指针是否合法?

Is it legal to compare dangling pointers?

提问人:fredoverflow 提问时间:6/7/2015 最后编辑:Baum mit Augenfredoverflow 更新时间:10/4/2017 访问量:2853

问:

比较悬空指针是否合法?

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

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

C++ language-lawyer 悬空指针

评论

11赞 6/7/2015
定义“合法”。
6赞 Puppy 6/7/2015
至少不是未定义的行为。
45赞 fredoverflow 6/7/2015
@rightfold 我是否冒着被语言律师终止的风险?
5赞 Marc Glisse 6/7/2015
作为一个数据点,gcc 优化为 .int*f(){int a;return &a;}return 0;
5赞 Ed Heal 6/7/2015
我想知道这样做有什么用

答:

59赞 M.M 6/7/2015 #1

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

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

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

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 中接受了评估结果和实现定义的结果,并且实现没有定义发生硬件陷阱;[expr.eq]/2说道:pq

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

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

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


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

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

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

在 N4430 中,文本从该部分向上移动了一级,因此它确实清楚地适用于所有存储持续时间。附上一条注释:

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


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


评论

24赞 Griwes 6/7/2015
@LightnessRacesinOrbit 请给我买一份标准的副本,这样我就可以这样做了(如果你能给我邮寄一份打印的副本,那就太好了,这样我就可以在我的答案中显示实际的标准,而不仅仅是它的内容,这似乎与你无关(我的意思是内容))。顺便说一句,菲利普说他也会对印刷版感兴趣。
19赞 Puppy 6/7/2015
我们其他人不买标准。我们引用最新的免费草案,通常是FDIS左右,但此类事项的措辞往往不会有太大变化。
19赞 zwol 6/7/2015
@LightnessRacesinOrbit 如果您知道 Nxxxx 文档、FDIS 和官方标准之间的区别,那么您应该识别与官方标准最接近的近似值相对应的 N 号,该标准可免费在线公开获得。期望人们花费几百美元只是为了在相当于酒吧赌注的论点中更有说服力是荒谬的。
13赞 Steve Jessop 6/7/2015
@zwol:实际上,规定任何进入壁垒是相当合理的,以便在相当于酒吧赌注的论点中击落某人。关键是要赢,而不是要正确;-)如果得到正确的答案是重点,那么 Lightness 当然可以说“......并且发布的标准相同/不同“,而不是试图在不替换它的情况下诋毁引文。我的意思是,我认为 Lightness 是对的,但 Filip 引用的问题在于它们不支持他的主张,而不是它们不准确。
9赞 M.M 6/7/2015
@LightnessRacesinOrbit 就我个人而言,我对 N3936 的报价很好,除非有人拥有 C++14 的副本专门介入并指出差异(其中,AFAIK,没有)。C++11 和 N3337 也是如此。
4赞 supercat 6/9/2015 #2

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

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

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

-3赞 user2038893 6/10/2015 #3

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

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

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

评论

0赞 Mark Hurd 6/12/2015
它不仅仅是“合法的”,而是“实现定义的”。
1赞 user2038893 6/15/2015
(p == q) 的结果是“实现定义”,我同意。