我在我们的代码库中发现了典型的new/delete不匹配错误,如下所示:
char *foo = new char[10]; // do something delete foo; // instead of delete[] foo;
这有多严重?会导致内存泄漏或错误吗?后果是什么?我们有一些内存问题,但这似乎还没有严重到足以解释所有症状(堆损坏等)编辑:额外问题以澄清它是否只释放数组的第一个成员?或者它是否会使系统失去对阵列的跟踪?或者内存损坏是怎么回事?
tnkciper1#
这是未定义的严重行为(它可能工作,可能崩溃,可能做其他事情)。
j2cgzkjk2#
乍一看,调用delete而不是delete[]应该不是很糟糕:你破坏了第一个对象并引发了内存泄漏。
delete
delete[]
free
new
new[]
在new[]返回的地址上调用free会引发崩溃(它混乱地释放内存)。请参阅这些非常有启发性的链接以更好地理解:http://blogs.msdn.com/b/oldnewthing/archive/2004/02/03/66660.aspx#66782http://web.archive.org/web/20080703153358/http://taossa.com/index.php/2007/01/03/attacking-delete-and-delete-in-c从这些文章中还可以明显看出,为什么调用delete[]而不是delete也是一个非常糟糕的主意。因此,要回答:是的,这是一个非常非常严重的错误。2它破坏了内存(只在调用了第一个对象的析构函数之后)。
rekjcdws3#
对于new[],实现通常在某处存储分配的数组元素的数目,因为它们需要知道delete[]将析构其中的多少。比较new/delete和new[]/delete的单个对象的分配和解除分配:https://godbolt.org/z/GYTh7f7Y7。可以清楚地看到,后一种情况下的机器码要复杂得多。注意,new[]使用mov QWORD PTR [rax], 1将元素的数量(1)存储到分配内存的开头。delete[]然后使用mov rsi, QWORD PTR [rdi-8]读取该数量,以便能够迭代元素并调用它们的析构函数。普通的new不存储这个数字,因此,当您将new与delete[]一起使用时,delete[]将读取一些未指定的数字,并将析构函数应用到未预测的内存中。相反的new[]加上delete的情况也是非常错误的。普通的new表达式通常返回一个指针,该指针精确地指向operator new内部分配的内存块(通常调用malloc)。当该指针传递给delete表达式时,然后在内部将其传递给operator delete释放函数。但是new[]的情况就不同了,new[]不返回operator new内部获得的指针,而是返回增加了8字节的指针(使用GCC,但我认为x86_64上的Clang和MSVC也是如此)。请参见链接程序集中的lea r12, [rax+8]。在这8个字节中,存储分配的数组元素的数目。因此,如果将delete应用于通过new[]获得的内容,则delete将向operator delete传递一个尚未通过operator new分配的指针,因为它不会从中减去这8个字节。这最终可能会导致一些类似堆损坏的问题。
mov QWORD PTR [rax], 1
mov rsi, QWORD PTR [rdi-8]
operator new
malloc
operator delete
lea r12, [rax+8]
axzmvihb4#
正如在其他回答中指出的,new []与delete不匹配可能会崩溃。然而,如果被new 'd或delete' d的类型没有析构函数,那么至少在GCC,MSVC和Clang中,使用new/delete操作符方法的默认实现,将不会有任何结果。这包括基本类型int,char等。复杂类的new[]与delete(或new与delete[])不匹配可能会崩溃。这是因为如果编译器不需要调用析构函数,它就不会在数据之前插入元素计数。修改@丹尼尔Langr的godbolt页面,注意new/delete和new[]/delete[]在类没有析构函数时的有限差异:https://godbolt.org/z/hW4f3Ge13(与具有析构函数时相比:https://godbolt.org/z/GYTh7f7Y7)唯一的区别是对operator new和operator new[]的调用以及类似的删除操作。默认情况下,这些函数具有相同的实现。如果您试图确定堆损坏的原因,这将非常有用。
new []
new/delete
new[]/delete[]
operator new[]
4条答案
按热度按时间tnkciper1#
这是未定义的严重行为(它可能工作,可能崩溃,可能做其他事情)。
j2cgzkjk2#
乍一看,调用
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[]
而不是delete也是一个非常糟糕的主意。因此,要回答:是的,这是一个非常非常严重的错误。2它破坏了内存(只在调用了第一个对象的析构函数之后)。
rekjcdws3#
对于
new[]
,实现通常在某处存储分配的数组元素的数目,因为它们需要知道delete[]
将析构其中的多少。比较
new
/delete
和new[]
/delete
的单个对象的分配和解除分配:https://godbolt.org/z/GYTh7f7Y7。可以清楚地看到,后一种情况下的机器码要复杂得多。注意,new[]
使用mov QWORD PTR [rax], 1
将元素的数量(1)存储到分配内存的开头。delete[]
然后使用mov rsi, QWORD PTR [rdi-8]
读取该数量,以便能够迭代元素并调用它们的析构函数。普通的
new
不存储这个数字,因此,当您将new
与delete[]
一起使用时,delete[]
将读取一些未指定的数字,并将析构函数应用到未预测的内存中。相反的
new[]
加上delete
的情况也是非常错误的。普通的new
表达式通常返回一个指针,该指针精确地指向operator new
内部分配的内存块(通常调用malloc
)。当该指针传递给delete
表达式时,然后在内部将其传递给operator delete
释放函数。但是
new[]
的情况就不同了,new[]
不返回operator new
内部获得的指针,而是返回增加了8字节的指针(使用GCC,但我认为x86_64上的Clang和MSVC也是如此)。请参见链接程序集中的lea r12, [rax+8]
。在这8个字节中,存储分配的数组元素的数目。因此,如果将delete
应用于通过new[]
获得的内容,则delete
将向operator delete
传递一个尚未通过operator new
分配的指针,因为它不会从中减去这8个字节。这最终可能会导致一些类似堆损坏的问题。axzmvihb4#
正如在其他回答中指出的,
new []
与delete
不匹配可能会崩溃。然而,如果被new 'd或delete' d的类型没有析构函数,那么至少在GCC,MSVC和Clang中,使用new/delete操作符方法的默认实现,将不会有任何结果。这包括基本类型int,char等。
复杂类的
new[]
与delete
(或new
与delete[]
)不匹配可能会崩溃。这是因为如果编译器不需要调用析构函数,它就不会在数据之前插入元素计数。
修改@丹尼尔Langr的godbolt页面,注意
new/delete
和new[]/delete[]
在类没有析构函数时的有限差异:https://godbolt.org/z/hW4f3Ge13(与具有析构函数时相比:https://godbolt.org/z/GYTh7f7Y7)唯一的区别是对operator new
和operator new[]
的调用以及类似的删除操作。默认情况下,这些函数具有相同的实现。如果您试图确定堆损坏的原因,这将非常有用。