【问题标题】:Why does my program crash when I increment a pointer and then delete it?当我增加一个指针然后删除它时,为什么我的程序会崩溃?
【发布时间】:2017-05-02 05:34:06
【问题描述】:

当我意识到我对指针有很大的误解时,我正在解决一些编程练习。请有人解释一下这段代码导致 C++ 崩溃的原因。

#include <iostream>

int main()
{
    int* someInts = new int[5];

    someInts[0] = 1; 
    someInts[1] = 1;

    std::cout << *someInts;
    someInts++; //This line causes program to crash 

    delete[] someInts;
    return 0;
}

P.S 我知道这里没有理由使用“新”,我只是让示例尽可能小。

【问题讨论】:

  • 你能解释一下为什么你认为这段代码应该工作,你希望它产生什么?也许你认为只删除部分分配的内存是可能的?或者,调用 delete 运算符可能会与指向分配对象内部的任何指针一起工作(无论确切在哪里)?或许还有别的,请详细说明。
  • 到目前为止,每个人都在解释正确但有点不透明的内存语义。不幸的是,因为这显然是一个初学者问题,而 OP 几乎可以肯定 意味着 (*someInts)++;.
  • 为什么不会崩溃?
  • @imallett 我认为你可能是对的。但是,我当然没有那样解释问题! OP 对程序试图做什么的一些解释会很好......
  • @philipxy 实际上,他们的代码是正确的 MCVE 并演示了确切的问题,而带有注释的行 is 是直接导致崩溃的那个。 (但是,正如您所指出的,它崩溃的不是 where 行。)delete[] 需要一个从相应数组 new 获得的指针。递增从数组new 获得的指针,然后将结果指针传递给delete[],是无效的并导致UB。正如他们正确指出的那样,someInts++; 是问题所在。

标签: c++ pointers dynamic-memory-allocation delete-operator


【解决方案1】:

实际上是你标记为导致程序崩溃的语句之后的语句导致程序崩溃!

必须将相同的指针传递给delete[],就像您从new[]返回时一样。

否则程序的行为是未定义的

【讨论】:

  • 以前的 cmets 提到了 C 标准,但它们已被删除。
  • 是的,我的回复是对一条已删除的评论,基本上说“C 有效,为什么 C++ 无效?”如果您愿意,我现在可以删除这些 cmets。
  • 在 C++ 中,不仅地址必须相同,而且指针类型必须相同(或带有虚拟析构函数的基类)。
  • @Phil1970:对于数组删除 (delete[]),它必须是完全相同的指针类型,不允许多态。
  • 出于参考目的,大多数编译器将一些数据存储在分配给new的数据前面,并使用delete中的数据来管理内存适当地。当您增加指针然后deleteed 时,编译器开始在错误的位置查找其簿记数据。它使用了在那里找到的任何数据,这会导致您的段错误。
【解决方案2】:

问题在于,使用someInts++;,您将数组的第二个元素的地址传递给delete[] 语句。您需要传递第一个(原始)元素的地址:

int* someInts = new int[5];
int* originalInts = someInts; // points to the first element
someInts[0] = 1;
someInts[1] = 1;

std::cout << *someInts;
someInts++; // points at the second element now

delete[] originalInts;

【讨论】:

    【解决方案3】:

    这里不讨论具体实现的细节,崩溃背后的直观原因可以简单地通过考虑delete[]应该做什么来解释:

    销毁由new[]-表达式创建的数组

    你给delete[] 一个指向数组的指针。除此之外,它必须释放它分配的内存来保存该数组的内容。

    分配器如何知道要释放什么?它使用您给它的指针作为键来查找包含已分配块的簿记信息的数据结构。在某个地方,有一个结构存储了指向先前分配的块的指针和相关的簿记操作之间的映射。

    如果您传递给delete [] 的指针不是由相应的new[] 返回的指针,您可能希望此查找产生某种友好的错误消息,但标准中没有任何东西可以保证这一点。

    因此,有可能给定一个以前没有被new[] 分配的指针,delete[] 最终会看到一些确实不是一致的簿记结构的东西。电线交叉。发生崩溃。

    或者,您可能希望delete[] 会说“嘿,看起来这个指针指向我之前分配的区域内的某个位置。让我回去找到我在分配该区域时返回的指针并使用它查询簿记信息”,但同样,标准中没有这样的要求:

    对于第二种(数组)形式,表达式必须是一个空指针值或先前通过new-expression的数组形式获得的指针值。如果表达式是其他任何东西,包括如果它是通过new-expression的非数组形式获得的指针,行为未定义。 [强调我的]

    在这种情况下,你很幸运,因为你发现自己做错了事。

    PS:这是一个手动的解释

    【讨论】:

    • 有时用手波的解释没有错。其他答案很简洁,但我终于明白了为什么会发生这种情况。 (和@plugwash's :)
    • 通常有关分配的信息不会存储在单独的数据结构中,而是存储在分配内存之前。
    • 当事情出错时会发生什么非常重要。实际查找可能会返回“未找到”错误。一个简单的减法只会尝试访问用户指针下方内存中发生的任何内容。
    • 如果您传递给delete [] 的指针不是由相应的new[] 返回的指针,您可能希望此查找产生某种友好的错误消息,但其中没有任何内容保证这一点的标准。
    【解决方案4】:

    您可以在块中增加一个指针,并使用该增加的指针来访问块的不同部分,这很好。

    但是,您必须通过 Delete 从 New 中获得的指针。不是它的增量版本,也不是通过其他方式分配的指针。

    为什么?好吧,逃避的答案是因为这是标准所说的。

    实际的答案是因为要释放一块内存,内存管理器需要有关该块的信息。例如,它在哪里开始和结束,相邻块是否空闲(通常内存管理器将组合相邻的空闲块)以及它属于哪个区域(对于锁定多线程内存管理器很重要)。

    此信息通常存储在分配内存之前。内存管理器将从您的指针中减去一个固定值,并在该位置查找分配元数据的结构。

    如果您传递的指针不指向已分配内存块的开头,那么内存管理器会尝试执行减法并读取它的控制块,但它最终读取的不是有效的控制块。

    如果运气好,代码会很快崩溃,如果运气不好,最终可能会出现细微的内存损坏。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-25
      • 2022-12-29
      • 1970-01-01
      • 2012-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多