【问题标题】:Is it undefined behavior to dereference a dangling pointer?取消引用悬空指针是未定义的行为吗?
【发布时间】:2015-02-25 19:01:37
【问题描述】:

我在标准中找不到该程序未定义的地方:

#include <iostream>

int main() 
{
    int *p;
    {
        int n = 45;
        p = &n;
    }
    std::cout << *p;
}

§3.8 对象生命周期中的所有情况似乎都不适用于这里。

【问题讨论】:

  • 我相信p 仍然会指向n 所在的内存位置,但你无法知道那里会是什么。
  • @mstbaum 和这个问题有什么关系?
  • @remyabel (我之前删除的评论询问“无法预测结果”是否暗示 UB。)也许我不清楚什么是未定义的行为。我的推理可能与 mstbaum 的类似,因为我们不知道内存中的那个位置是什么,所以我们无法预测结果。这还不够吗?我是否需要在标准中查找才能确定?
  • @eigenchris 这些答案似乎都没有提到它是否是 UB(他们只是宣布它是或不是,没有证据)
  • @eigenchris 具有不确定值的对象具有不可预测的值。你会说这种行为是未定义的吗?不,因为标准规定了它。

标签: c++ language-lawyer undefined-behavior


【解决方案1】:

由于措辞,我不是 100% 确定,但看起来这已包含在 3.8/6 中(我认为这种解释正确的原因是因为 3.8/5 中的非规范示例,// undefined behavior, lifetime of *pb has ended ):

...在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,任何引用原始对象的glvalue都可以使用,但只能以有限的方式使用....程序有未定义的行为,如果:

那么第一个项目符号就是罪魁祸首:an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,:这种转换必须发生在调用operator&lt;&lt; 或最后读取整数值以在ostream 代码中格式化的点。

【讨论】:

  • 不知道这在标准中的位置,但由于 n 存在于此处的堆栈中,因此人们会认为内存超出范围时会被“释放”。
  • 另外,我对3.7.3(“自动存储时长”)的理解是,block退出后,对象占用的存储不需要存在。
  • 您能否确定存储何时重用或释放,因为它取决于该短语。特别是因为这需要发生在之前
  • 我会说当变量超出范围时会发生“存储被释放”,所以这不适用(参见 3.7.3/1)。该部分试图谈论诸如展示位置删除和新的情况
  • 回复。更新后的版本,“这样的glvalue”仍然是指“在对象占用的存储被重用或释放之前”
【解决方案2】:

*p 是一个左值。代码cout &lt;&lt; *p 需要左值到右值的转换。这是由 C++14 [conv.lval] 定义的。

第 2 点列出了各种情况并描述了每种情况下的行为。这些都不适用于*p。特别是最后一点是:

否则,glvalue指示的对象中包含的值就是prvalue结果。

但是,*p 并不表示对象。

在 [basic.life] 部分中,除了 [conv.lval] 中的内容之外,还有一些定义左值到右值转换的案例。这些情况与何时获得对象的存储有关,但我们超出了对象的生命周期。但是它们不适用于*p,因为在前一个块结束时会释放存储空间。

因此,此代码的行为是因省略而未定义:标准中没有任何地方定义当左值不表示对象且不表示有效存储时执行右值转换的含义对于一个对象。


“由于遗漏而未定义”可能会让人感到不满意,我们总是希望看到一个具体的陈述“这是未定义的行为”,以确保我们没有忽略某些东西。但有时就是这样。

【讨论】:

    【解决方案3】:

    这当然是未定义的行为(根据常识和标准的措辞)。

    就标准而言,3.8/5 对允许和不允许的内容相当具体:

    [...] 在对象的生命周期结束之后并且对象占用的存储空间被重用或释放之前,任何指向对象所在存储位置的指针或可以使用,但只能以有限的方式使用 [...] 并使用指针,就好像指针是 void* 类型一样, 定义明确
    间接 [...] 是允许的 [...],如下所述。程序具有未定义的行为,如果
    - ...
    - [...] 用作 static_cast 的操作数,除非转换是指向 cv void 的指针,或指向 cv void 的指针,然后是指向 cv char 或 cv unsigned char 的指针
    - [...] 用作 dynamic_cast 的操作数

    对象的存储在 3.7.3/1 的作用域结束时结束(实际上这很可能不是真的,堆栈帧可能会在函数结束时被重置,但正式 em> 就是这样)。因此,解引用不会发生在生命周期结束之后但在存储释放之前。它发生在存储释放之后。
    因此,您无论如何都可以取消引用指针的特殊条件不适用(对于任何具有相同前提条件的类似段落,例如 3.8/6,也是如此)。

    进一步,假设上一段不正确,只允许在取消引用之前将指针取消引用为 cv void* 或将其转换为 cv char(有符号或无符号)。换句话说,您不允许查看指向的int,就好像它是int。如 3.8/5 中所述,int* 实际上只是对象生命周期后的void*。这意味着将其取消引用为 int* 相当于进行强制转换(不是明确的,但仍然如此)。

    真的希望这种尝试会产生错误,但我想编译器很难检测到这一点。指针本身完好无损,并且它是通过获取有效对象的地址安全派生的,这可能几乎无法诊断。

    【讨论】:

      【解决方案4】:

      所以首先根据3.7.3 Automatic storage duration释放你的对象的存储:

      显式声明或不显式注册的块范围变量 声明的静态或外部具有自动存储持续时间。存储 这些实体持续到创建它们的块 退出。

      3.8 对象生命周期

      在对象的生命周期开始之前但在存储之后 对象将占用的位置已被分配,或者在 对象的生命周期已经结束并且在存储之前 被占用的对象被重用或释放,任何指向该对象的指针 可以使用对象将要或曾经所在的存储位置 但仅限于有限的方式

      所以解引用指向变量的指针,释放的存储导致UB

      【讨论】:

        【解决方案5】:

        是的,这是未定义的行为。

        n 具有自动存储期限参见[basic.stc.auto]p1

        属于块或参数范围且未显式声明为 static、thread_local 或 extern 的变量具有自动存储持续时间。 这些实体的存储将持续到创建它们的块退出。

        当块退出时存储结束,在这种情况下是。

        我们可以从[basic.stc]p4 中看到p 是一个无效指针,而通过无效指针间接是未定义的行为:

        当一个存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针的值都变为无效指针值。 通过无效指针值的间接传递以及将无效指针值传递给释放函数具有未定义的行为。 对无效指针值的任何其他使用都具有实现定义的行为。26

        为了完整起见,如果我们查看[basic.compund]p3,我们会看到指针类型有四个值:

        指针类型的每个值都是以下之一:

        • 指向对象或函数的指针(据说该指针指向对象或函数),或
        • 超过对象末尾的指针 ([expr.add]),或
        • 该类型的空指针值,或
        • 一个无效的指针值。

        \expr.unary]p1 告诉我们表达式unary * 应用于应该是一个对象类型或一个指向的指针函数类型

        一元*运算符执行间接:应用它的表达式应该是一个指向对象类型的指针,或者是一个指向函数类型的指针,结果是一个左值,指向表达式指向的对象或函数. 如果表达式的类型是“指向 T 的指针”,则结果的类型是“T”。

        由于 无效指针 不是这两个,我们有未定义的行为。

        【讨论】:

        • 有趣的是,我正在回顾旧的 C++ 草案,这种措辞在 C++11 和 C++14 草案中都不存在,但出现在 C++17 草案中。这就是为什么在 2015 年没有人发现这个。
        • 看起来这是p0137r1添加到地址DR 1776
        • 你能证明间接是通过一个无效的指针值而不是其他东西发生的吗?
        • @LanguageLawyer 我不确定您在寻找什么,n 的存储已超出第一个报价的持续时间,p 指向n 的存储和第二个报价这是一个无效的指针。你是想让我更具体一点吗?
        • 一元间接运算符* 需要一个值。 [由]p 表示的对象包含一个[n 无效指针] 值,但这个表达式原本并没有指定该值。它属于错误的值类别。应该怎么做才能让* 开心?它如何与无效的指针值交互?
        猜你喜欢
        • 1970-01-01
        • 2011-10-11
        • 2017-02-17
        • 2011-10-31
        • 2018-07-11
        • 1970-01-01
        • 2013-01-21
        • 2023-03-21
        • 2019-07-22
        相关资源
        最近更新 更多