为啥这段代码不会导致内存泄漏? [复制]

Posted

技术标签:

【中文标题】为啥这段代码不会导致内存泄漏? [复制]【英文标题】:Why does this code not result in a memory leak? [duplicate]为什么这段代码不会导致内存泄漏? [复制] 【发布时间】:2013-10-03 09:35:34 【问题描述】:

我用valgrind--leak-check=full 检查了以下C++ 代码,它说没有内存泄漏。这是为什么呢?

char *p = new char[256];
delete p;

据我所知new[] 应该与delete[] 匹配。

【问题讨论】:

未定义的行为是未定义的,包括它可以工作。 难道 valgrind 不应该足够聪明地指出这一点吗?这是我可以为内存泄漏编写的最简单的测试,我希望至少有一个警告...... @DanLincan:没有内存泄漏。类似 lint 的工具可能会捡起它。 @DanLincan:你可能需要一个静态分析器来解决这个问题,它通常需要比大多数编译器都费心去追踪它的元数据。 valgrind memcheck 确实注意到了这一点,但没有将其归类为泄漏。 【参考方案1】:

尽管正如@KillianDS 所说,这是未定义的行为,但差异可能与deletedelete[] 都释放底层内存的事实有关。 delete[] 的要点是,在释放内存之前调用数组中每个对象的析构函数。由于char 是POD 并且没有析构函数,因此在这种情况下两者之间没有任何实际区别。

但是,您绝对不应该依赖它。

【讨论】:

使用非平凡析构函数 delete 会崩溃,因为它在数组的开头存储元素的计数,因此指针不等于底层 free 期望的值,并且free 将引发致命断言。不同的编译器和标准 C++ 库的做法不同,但 AFAIR gcc 会这样做。 @JanHudec:“将”夸大其词了。编译器和生成的代码可以为所欲为。包括看起来在工作。 @cHao:没有夸大其词(对于 gcc),因为我查看了它生成的内容。如果您不相信我,请参阅here。 (请注意,他们只有在增加 ABI 版本时才能更改此设置,而他们已经有一段时间没有这样做了。) @JanHudec:他们可以随时更改它,并且仍然具有符合要求的实现;他们承诺会或不会做的事情完全是另一回事,与 C++ 无关(绝对没有任何承诺)。 @cHao:是的,他们可以改变它。但是当他们改变它时,他们必须改变 ABI 版本,否则他们会破坏很多东西。【参考方案2】:

deletedelete[] 仅当 p 指向基本数据类型(例如 char 或 int)时才相等。

如果p 指向一个对象数组,结果会有所不同。试试下面的代码:

class T 
public:
    T()  cout << "constructor" << endl; 
    ~T()  cout << "destructor" << endl; 
;

int main()

    const int NUM = 3;
    T* p1 = new T[NUM];

    cout << p1 << endl;
    //  delete[] p1;
    delete p1;

    T* p2 = new T[NUM];
    cout << p2 << endl;
    delete[] p2;

通过使用delete[],将调用数组中 T 的所有析构函数。通过使用delete,只会调用p[0] 的析构函数。

【讨论】:

否则它可能会崩溃并烧毁您的 PC,毕竟这是未定义的行为。根据实现 new[] 可以使用分配缓冲区的前 8 个字节来存储元素的数量,然后返回缓冲区内的偏移量;在这种情况下,delete 会将偏移地址传递给free,这可能导致free 找不到相应的缓冲区... @MatthieuM.: IIRC 在使用 gcc 编译时 崩溃(并牺牲你的 PC),因为元素的数量写在数组的开头,因此与底层 malloc() 返回的内容相比,指针发生了移动。它在使用 MSVC++ 编译时不会崩溃,这显然是从通用块元数据中得出的计数。不同之处在于微软有一个用于 C 和 C++ 的库,因此 C++ 依赖于 C 的实现细节,而 GNU stdlibc++ 尊重分层并且仅依赖于 C 库的公共接口。【参考方案3】:

当我尝试这个时,valgrind 报告:

==22707== Mismatched free() / delete / delete []
==22707==    at 0x4C2B59C: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22707==    by 0x40066D: main (in /home/andrew/***/memtest)
==22707==  Address 0x5a1a040 is 0 bytes inside a block of size 256 alloc'd
==22707==    at 0x4C2C037: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22707==    by 0x40065D: main (in /home/andrew/***/memtest)

这并不是真正的内存泄漏,但 valgrind 确实注意到了这个问题。

【讨论】:

【参考方案4】:

因为它是未定义的行为。在您的情况下,delete 可能会在您的编译器中完成 delete [] 的工作,但它可能无法在另一台机器上工作。

【讨论】:

【参考方案5】:

这是undefined behavior,所以我们无法推断它的行为。如果我们查看 C++ 标准部分草案3.7.4.2释放函数,第 3 段说(强调我的):

[...] 否则,如果提供给标准库中 operator delete(void*) 的值不是先前调用任一 operator new(std: :size_t) 或标准库中的 operator new(std::size_t, conststd::nothrow_t&) ,如果提供给标准库中的 operator delete[] (void*) 的值不是,则行为未定义先前调用 operator new[] (std::size_t) 或 operator new[] (std::size_t, const std::nothrow_t&) 返回的值之一 标准库。

实际细节将是implementation-defined behavior,可能会有很大差异。

【讨论】:

【参考方案6】:

deletedelete [] 的区别在于编译器添加了代码来调用析构函数来删除对象。说这样的话:

    class A
    
        int a;
        public:
            ...
            ~A()  cout<<"D'tor"; 
    ;

    a=new A[23];
    delete [] a; 

这个delete [] a; 被转换成类似的东西,

   for (int i=0; i<23; i++)
   
       a[i].A::~A();
   
   std::delete a;

因此,对于您的情况,由于它是内置数据类型,因此无需调用析构函数。所以,它变成了,

   std::delete a;

哪个实习生调用free() 来释放内存。这就是你没有得到任何泄漏的原因。由于在 g++ 中使用 free() 完全释放了分配的内存。

但最佳做法是,如果您使用new [],则必须使用delete [] 版本。

【讨论】:

以上是关于为啥这段代码不会导致内存泄漏? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 INotifyCollectionChanged 会导致内存泄漏?

为啥这会导致内存泄漏?

为啥 pthread 会导致内存泄漏

为啥使用“新”会导致内存泄漏?

UITabBarItem 内存泄漏

为啥 Netty ByteBuf.readBytes 会导致内存泄漏?