指向数组的指针p使用delete p总是会导致内存泄漏吗?

7

在一次软件会议的讨论中,我着手调查使用普通的delete删除动态分配的原始数组是否会导致内存泄漏。

我编写了这个小程序,并在运行Windows XP上的Visual Studio 2008进行了编译:

#include "stdafx.h"
#include "Windows.h"

const unsigned long BLOCK_SIZE = 1024*100000;
int _tmain()
{
    for (unsigned int i =0; i < 1024*1000; i++)
    {
        int* p = new  int[1024*100000];
        for (int j =0;j<BLOCK_SIZE;j++) p[j]= j % 2;
        Sleep(1000);
        delete p;
    }
}

我随后使用任务管理器监测了我的应用程序的内存消耗,令人惊讶的是,内存被正确地分配和释放,分配的内存并没有像预期的那样稳定增加。

我修改了我的测试程序以分配非基元类型的数组:

#include "stdafx.h"
#include "Windows.h"


struct aStruct
{
    aStruct() : i(1), j(0) {}

    int i;
    char j;
} NonePrimitive;

const unsigned long BLOCK_SIZE = 1024*100000;
int _tmain()
{
    for (unsigned int i =0; i < 1024*100000; i++)
    {
        aStruct* p = new  aStruct[1024*100000];
        Sleep(1000);
        delete p;

    }
}

运行了10分钟后,内存没有明显增加。

我使用警告级别4编译项目,并没有收到警告。

在Visual Studio运行时,是否可能跟踪分配的对象类型,因此在该环境中deletedelete[]之间没有区别?


重复的问题?https://dev59.com/UXI-5IYBdhLWcg3wVWpi - Philip Potter
@Philip Potter:不是那个问题——那个问题特别是关于导致内存泄漏的。而在这种情况下,内存泄漏并不典型。 - sharptooth
尝试使用指向不同分配的int的shared_ptr<int>数组进行测试,您将看到您的实现是否使deletedelete[]等效。在C ++中总是对人们知道是错误的事情产生着迷,明确声明为错误的事情,但似乎有时可能会起作用;-) - Steve Jessop
你引用的那个东西如果代码只是针对MSVC而言,那么它就是愚蠢的。但正确的规则是“在标准中未定义,因此不应使用除非Microsoft定义了行为”。这个错误的安全感开发者正在使用规则“在标准中未定义,因此只要我运行它时不崩溃/泄漏/失败,就可以使用它”。即使有一组好的单元测试,这对于C++来说也不够好-如果依赖未定义的行为,你只是在积累麻烦。未来的一个小改变可能会改变行为并触发大量重写。 - Steve Jessop
1
更不用说没有任何开发人员能够证明这种说法:“啊,是的,我完全理解deletedelete[]之间的区别,但对于仅在MSVC代码中使用POD类型数组时,我聪明地故意使用delete,以优化节省两个额外字符的输入。”你并没有,你只是忘记了。承认错误,纠正它,停止浪费时间争论;-) - Steve Jessop
显示剩余2条评论
8个回答

19

删除数组p,其中p是一个数组,被称为未定义的行为。

具体来说,当您分配原始数据类型(如整数)的数组时,编译器没有太多工作要做,因此将其转换为简单的malloc(),所以delete p可能有效。

通常情况下,delete p将会失败,原因可能是:

  • p是一个复杂数据类型 - delete p; 不知道如何调用各个析构函数。
  • "用户"重载了operator new[]和delete[]以使用不同于常规堆的堆。
  • 调试运行时重载了operator new[]和delete[]以添加额外的跟踪信息以供数组使用。
  • 编译器决定需要存储额外的RTTI信息与对象一起,而delete p; 将无法理解,但delete []p;可以。

17

不行,这是未定义的行为。不要这样做 - 使用delete[]

在VC++7到9版本中,如果涉及类型具有平凡析构函数,则可以运行,但在更新的版本上可能停止工作 - 这是未定义行为的常见情况。无论如何都不要这样做。


在@Eli的情况下为什么可能会起作用的好解释! - Ogre Psalm33

3

这被称为未定义行为;虽然它可能有效,但你不知道为什么,所以你不应该坚持使用它。

我认为Visual Studio没有跟踪你如何分配对象(是数组还是普通对象),也不会神奇地在你的删除操作中添加[]。它可能将delete p;编译成与p = new int相同的代码,并且出于某种原因它确实起作用。但你不知道为什么。


3
一种答案是,是的,它可能会导致内存泄漏,因为它不会为数组中的每个项目调用析构函数。这意味着数组中项目所拥有的任何额外内存都将泄漏。
更符合标准的答案是,这是未定义行为。例如,编译器有权为数组使用与非数组项不同的内存池。以一种方式进行new操作,但以另一种方式进行delete操作可能会导致堆损坏。
您的编译器可能会做出标准没有做出的保证,但第一个问题仍然存在。对于不拥有附加内存(或资源,如文件句柄)的POD项,您可能没问题。
即使对于您的编译器和数据项来说是安全的,也不要这样做——这会误导任何试图阅读您代码的人。

2
不,处理数组时应该使用delete[]

2

仅使用delete不会调用数组中对象的析构函数。尽管它可能按预期工作,但由于它们的工作方式存在一些差异,因此这是未定义的。因此,您不应该使用它,即使对于内置类型也是如此。(参考链接)


常见问题解答有一个非常有力的论点,感谢您指出。 - Eli

1

使用 delete [] 告诉编译器对数组的每个元素调用析构函数。 如果在对象数组上使用未使用 delete [] 可能会导致内存泄漏,这是由于动态内存分配造成的,如下所示:

class AClass
{
public:
    AClass()
    {
        aString = new char[100];
    }
    ~AClass()
    {
        delete [] aString;
    }
private:
    const char *aString;
};

int main()
{
    AClass * p = new  AClass[1000];
    delete p; // wrong
    return 0;
}

1

原因似乎不会泄漏内存,是因为delete通常基于free,它已经知道需要释放多少内存。然而,C++部分可能无法正确清理。我敢打赌只有第一个对象的析构函数被调用了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接