为什么当我递增一个指针然后删除它时我的程序会崩溃?

Why does my program crash when I increment a pointer and then delete it?

当我意识到我对指针有很大的误解时,我正在解决一些编程问题。请有人解释此代码导致 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 我知道没有理由在这里使用 "new",我只是让示例尽可能小。

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

必须 在从 new[] 返回时将相同的指针传递给 delete[]

否则程序的行为是未定义

问题在于,使用 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;

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

Destroys an array created by a new[]-expression

你给 delete[] 一个指向数组的指针。除其他事项外,它必须释放分配给之后用于保存该数组内容的内存。

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

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

所以,有可能,给定一个之前未被 new[] 分配的指针,delete[] 最终会查看一些确实不一致的簿记结构。电线交叉。发生崩溃。

或者,您可能希望 delete[] 会说 "hey, it looks like this pointer points to somewhere inside a region I allocated before. Let me go back and find the pointer I returned when I allocated that region and use that to look up the bookkeeping information" 但是,同样,标准中没有这样的要求:

For the second (array) form, expression must be a null pointer value or a pointer value previously obtained by an array form of new-expression. If expression is anything else, including if it's a pointer obtained by the non-array form of new-expression, the behavior is undefined. [emphasis mine]

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

PS: 这是手摇解释

您可以在块内递增指针并使用递增的指针访问块的不同部分,这很好。

但是你必须通过删除你从New得到的指针。不是它的增量版本,不是通过其他方式分配的指针。

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

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

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

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

如果运气好,代码会很快崩溃,如果运气不好,则可能会导致轻微的内存损坏。