新建/删除运算符不匹配错误的严重程度

How serious is the new/delete operator mismatch error?

本文关键字:程度 错误 不匹配 删除 运算符 新建      更新时间:2023-10-16

我在我们的代码库中发现了典型的新/删除不匹配错误,如下所示:

char *foo = new char[10];
// do something
delete foo; // instead of delete[] foo;

这到底有多严重?它是否会导致内存泄漏或错误?后果是什么。我们有一些内存问题,但这似乎不足以解释我们的所有症状(堆损坏等)

编辑:为清晰起见,需要额外提问
它只是释放数组的第一个成员吗?或
它是否会使系统失去对阵列的跟踪?或
内存损坏是怎么回事?

它是未定义的严重行为(它可能工作,它可能崩溃,它可能做其他事情)。

乍一看,调用delete而不是delete[]应该不会很糟糕:您破坏了第一个对象,并引发了一些内存泄漏。

但是:然后,delete(或delete[])调用free来释放内存。而free需要其原始分配的地址,才能正确释放内存。或者,当new返回malloc分配的原始地址时,new[]返回一个不同的地址。

new[]返回的地址调用free会引发崩溃(它混乱地释放内存)。

查看这些非常有指导意义的链接以更好地理解:

http://blogs.msdn.com/b/oldnewthing/archive/2004/02/03/66660.aspx#66782

http://web.archive.org/web/20080703153358/http://taossa.com/index.php/2007/01/03/attacking-delete-and-delete-in-c

从这些文章中也可以明显看出,为什么调用delete[]而不是删除也是一个非常糟糕的主意。

所以,要回答:是的,这是一个非常非常严重的错误。它破坏内存(仅在调用第一个对象的析构函数之后)。

情况非常严重。对于new[],实现通常将分配的数组元素的数量存储在某个地方,因为它们需要知道delete[]将破坏其中的多少。

将单个对象的分配和释放与new/deletenew[]/delete进行比较:https://godbolt.org/z/GYTh7f7Y7.您可以清楚地看到,后一种情况下的机器代码要复杂得多。注意,new[]将元素的数目(1)存储到具有mov QWORD PTR [rax], 1的所分配存储器的开头。delete[]然后用mov rsi, QWORD PTR [rdi-8]读取这个数字,以便能够迭代元素并调用它们的析构函数。

普通的new不存储这个数字,因此,当您将newdelete[]一起使用时,delete[]将读取一些未指定的数字,并对未预测的内存应用析构函数。这可能会造成严重的漏洞问题。

相反的new[]delete的情况也是非常错误的。普通的new表达式通常返回一个指针,该指针精确地指向operator new(通常调用malloc)内部分配的内存块。该指针在传递到delete表达式时,会按原样在内部传递给operator delete释放函数。

但CCD_ 32的情况并非如此。即,new[]不返回由operator new内部获得的指针。相反,它返回增加了8个字节的指针(使用GCC,但我认为Clang和x86_64上的MSVC也是如此)。请参见链接部件中的lea r12, [rax+8]。在这8个字节中,存储分配的数组元素的数量。因此,如果将delete应用于使用new[]获得的内容,delete将向operator delete传递一个未使用operator new分配的指针,因为它不会从中减去这8个字节。这最终可能会导致类似堆损坏的情况。

正如其他答案中所指出的,new []delete不匹配可能会崩溃。

但是,如果new'd或delete'd类型没有析构函数,那么至少对于GCC、MSVC和Clang,对于运算符new/delete方法的默认实现,不会有任何后果。这包括int、char等基本类型。

复杂类的new[]delete(或newdelete[])不匹配可能会崩溃。

这是因为如果编译器不需要调用析构函数,它就不会在数据之前插入元素计数。

修改@Daniel Langr的godbolt页面时,如果类没有析构函数,请注意new/deletenew[]/delete[]之间的差异有限:https://godbolt.org/z/hW4f3Ge13(与具有析构函数时相比:https://godbolt.org/z/GYTh7f7Y7)唯一的区别是对operator newoperator new[]的调用以及对delete的类似调用。默认情况下,这些函数具有相同的实现。

如果您试图确定堆损坏的原因,这将非常有用。