使用free()或delete而不是delete[]会有任何危险吗?

5

可能是重复问题:
(POD)释放内存:delete和delete[]相同吗?

delete是否会释放数组中第一个元素之后的元素?

char *s = new char[n];
delete s;

在上述情况下,由于所有s的元素都是连续分配的,并且不应该可能仅删除数组的一部分,因此这是否重要?
对于更复杂的类型,delete会调用第一个对象之外的对象的析构函数吗?
Object *p = new Object[n];
delete p;

如何使用delete[]推断除第一个以外的Object数量,这不意味着它必须知道分配内存区域的大小吗?如果内存区域由于性能原因留有一些余量怎么办?例如,人们可以假设并非所有分配器都提供单字节粒度。那么,任何特定的分配都可能超过每个元素所需的大小一个或多个元素。

对于像charint这样的基本类型,是否有任何区别:

int *p = new int[n];
delete p;
delete[] p;
free p;

除了各自通过“delete”->“free”释放机制采取的路线外,还有什么其他路线可以进行调用?

我不认为这是重复的,我在问一些非常具体的不同问题,并且对汇编内涵没有兴趣。 - Matt Joiner
1
不,这是重复的。你正在问“我可以用delete替换delete[]吗?”这完全相同的问题。答案与以前所有类似问题的答案相同:“不,你不能这样做,这是未定义行为”。 - jalf
如果你想问其他问题(比如“delete[] 如何知道要删除多少个对象”),那么请为此创建一个新问题,并给它一个独立的标题,这样其他想问同样问题的人就能找到它了。 - jalf
10个回答

18

这是未定义行为(很可能会破坏堆或立即崩溃程序),你永远不应该这样做。只能使用与分配该内存相对应的基元释放内存。

违反这条规则可能会导致正确功能的巧合,但一旦任何事情改变-编译器、运行时、编译器设置 ,程序就会崩溃。您永远不应该依赖这种正确的功能并期望它存在。

delete[]使用特定于编译器的服务数据来确定元素的数量。通常在调用 new [] 时会分配更大的块,在开头存储数字,并将地址给予调用者。但是 delete [] 依赖于被 new [] 分配的块,而不是其他任何东西。如果您将除 new [] 以外的任何内容与 delete [] 或反之运用,则会遇到未定义行为。


10

阅读常见问题解答:16.3 我可以使用free()释放使用new分配的指针吗?我可以使用delete释放使用malloc分配的指针吗?

在上述情况下,由于s的所有元素都是连续分配的,而且不可能仅删除数组的一部分,这是否重要?

是的,很重要。

如何让 delete[] 推断出除第一个元素外的对象数量?这不意味着它必须知道已分配内存区域的大小吗?

编译器需要知道。请参见FAQ 16.11

因为编译器存储该信息。

我的意思是编译器需要不同的 delete 来生成适当的记录代码。现在希望这一点已经清楚了。


3
如果编译器可以存储delete[]所需的信息,为什么它不能足够聪明地处理普通的delete? - vanja.
因为编译器存储了那些信息。这是一派胡言。operator new是否会保留所有分配的副本以及数组分配的元素计数?我认为这似乎不太可能,也非常低效。 - Matt Joiner
@Anacrolix:我指的是编译器为分配/释放管理的簿记信息。我不确定您所说的重复是什么意思。VS曾经在数组开始之前保留数组元素的数量。您能提供替代实现吗? - dirkgently
不是编译器存储那些信息,而是运行时。请更新您的答案,这个区别很重要。编译器无法存储该信息,因为在编译时它是未知的。 - sbk
@dirkgently:实际上,由于您可以重载new/delete运算符,因此您可以按照您希望的方式存储信息。实际上,大多数实现在第一个元素之前存储数组的大小,因此您通常可以通过小(负)偏移量找到它。 - Matthieu M.
显示剩余2条评论

4

是的,这很危险!

不要这样做!

这会导致程序崩溃或更糟糕的行为!

对于使用new分配的对象,您必须使用delete

对于使用new []分配的对象,您必须使用delete []

对于使用malloc()calloc()分配的对象,您必须使用free()

请注意,对于所有这些情况,删除/释放已经被删除/释放的指针是非法的。不能使用null调用free。使用NULL调用delete/delete[]是合法的。


1
这是众所周知的。我在询问具体情况。 - Matt Joiner
2
"free()函数释放由ptr指向的内存空间,该空间必须是之前调用malloc()、calloc()或realloc()返回的。否则,如果之前已经调用了free(ptr),或者ptr为NULL,则会发生未定义的行为。如果ptr为NULL,则不执行任何操作。"。请注意关于null的条款。来源于http://linux.die.net/man/3/free,但我手头没有正式的C规范。 - user166390
6
没有具体的案例。这是不允许的。请注意不要改变原意。 - jalf

3

是的,存在实际的危险。即使将实现细节放在一边,也要记住,operator new/operator deleteoperator new[]/operator delete[]函数可以完全独立地替换。因此,明智的做法是将new/deletenew[]/delete[]malloc/free等视为不同的、完全独立的内存分配方法,它们之间没有任何共同点。


即使它们可以独立替换,通常建议要么全部替换(包括放置新的),要么全部不替换。 - Matthieu M.
是的,也许吧。但即使它们都被替换了,它们仍然可以从不同的内存池中分配内存,这些内存池具有不同的内存管理原则(显而易见的原因是,即使是相同的对象类型,在“[]”函数中块大小也是可变参数,而在非“[]”函数中则不是),因此任何交叉使用都是完全不可行的。 - AnT stands with Russia

2
删除数组中第一个元素时,delete只会释放第一个元素的内存,无论在哪个编译器上执行都是如此。在某些情况下可能有效,但这是巧合。在上述情况下,由于s的所有元素都是连续分配的,因此删除数组的一部分应该是不可能的,因此这是否重要取决于内存如何标记为自由状态。对于更复杂的类型,delete不会调用第一个对象之后的对象的析构函数。
#include <cstdio>

class DelTest {
    static int next;
    int i;
public:
    DelTest() : i(next++) { printf("Allocated %d\n", i); }
    ~DelTest(){ printf("Deleted %d\n", i); }
};

int DelTest::next = 0;

int main(){
    DelTest *p = new DelTest[5];
    delete p;
    return 0;
}

如何使用delete[]删除第一个对象之外的对象,这是否意味着它必须知道所分配内存区域的大小?
答:是的,大小信息被存储在某个位置。存储位置取决于具体实现。例如,分配器可以将大小存储在分配地址之前的头部。
如果为了性能而在内存区域中分配了一些超额空间怎么办?例如,假设并非所有分配器都提供单字节的粒度。那么,任何特定分配可能会超过每个元素所需的大小一个完整元素或更多。
正因为如此,返回的地址被设置为对齐到字边界。可以使用sizeof操作符查看“超额空间”的大小,并且也适用于堆栈上的对象。
对于char、int等基本类型,malloc和new有什么区别吗?
答:有。 malloc和new可能使用不同的内存块。即使这不是事实,也最好不要假定它们是相同的。

我认为你的意思是new和malloc可能使用不同的分配器? - Matt Joiner
可能会有额外的“悬挂”。对齐与块的起始地址有关。出于性能原因,分配器可能使用更多的内存。例如,有些分配器只分配大小为2的幂次方的块。如果您请求33个字节,则会获得大小为64的块。这使得管理已分配和空闲块的列表变得更加容易/快速,但代价是增加了内存使用量。甚至可能分配器仅通过起始地址就知道分配的大小,从而避免了存储其他信息的需要。 - KeithB
@Anacrolix:你可能是对的,但通常分配器是操作系统。我的答案主要基于K&R中的分配器示例...我假设每个分配器从操作系统获取不同的内存块。 - Agnel Kurian

1
对于 char、int 等原始类型,它们之间有什么区别吗?
我认为你会得到未定义的行为。因此,你不应该指望稳定的行为。你应该始终使用 new/delete、new[]/delete[] 和 malloc/free 成对出现。

2
不,"implementation defined" 是ISO用来表示编译器供应商必须记录其选择的术语。在这种情况下没有这样的义务。Nasal deamons可以无需警告而被允许。 - MSalters
已经在这里讨论过了:https://dev59.com/JHI_5IYBdhLWcg3wDOdC - Naveen
我不知道ISO使用这样的术语。已修复。 - big-z

1

这是未定义行为。因此,答案是:是的,可能存在风险。而且无法准确预测什么会触发问题。即使有时可以运行,那么还能再次运行吗?它是否取决于类型?元素数量?


1

0

虽然在某些逻辑上看起来,你可以混合使用new[]和free或delete而不是delete[],但这是基于编译器相当简单的假设,即它将始终使用malloc()来实现new[]的内存分配。

问题在于,如果您的编译器具有足够智能的优化器,它可能会发现没有与您创建的对象的new[]相对应的"delete[]"。因此,它可能会假定它可以从任何地方获取其内存,包括堆栈,以节省调用new[]的真正malloc()的成本。然后,当您尝试调用free()或错误类型的delete时,它很可能会出现严重故障。


0

步骤1 阅读此内容: new-delete和malloc-free之间的区别是什么

您只看到了开发者端看到的内容。
您没有考虑标准库如何进行内存管理。

第一个区别是new和malloc从内存中的两个不同区域分配内存(New从FreeStore分配,malloc从Heap分配(不要关注名称,它们基本上都是堆,这些只是标准的官方名称))。如果您从一个区域分配并释放到另一个区域,则会破坏用于管理内存的数据结构(不能保证它们将使用相同的结构进行内存管理)。

当您像这样分配一个块时:

int*   x= new int; // 0x32

内存可能看起来像这样:但实际上不一定是这样的,因为我并没有花太多时间思考。

Memory   Value      Comment
0x08     0x40       // Chunk Size  
0x16     0x10000008 // Free list for Chunk size 40
0x24     0x08       // Block Size
0x32     ??         // Address returned by New.
0x40     0x08       // Pointer back to head block.
0x48     0x0x32     // Link to next item in a chain of somthing.

重点是,分配的块中有比你用于内存管理的int更多的信息。

标准没有规定如何实现这一点,因为(按照C/C++的风格),他们不想侵犯编译器/库制造商实现其体系结构最有效的内存管理方法的能力。

考虑到这一点,您希望制造商能够区分数组分配/释放和普通分配/释放,以便可以使两种类型都尽可能高效。因此,您不能混合使用,因为它们在内部可能使用不同的数据结构。

如果您实际分析C和C++应用程序之间的内存分配差异,您会发现它们非常不同。因此,使用完全不同的内存管理技术来优化应用程序类型并不是不合理的。这是在C++中更喜欢new而不是malloc()的另一个原因,因为它可能更有效(尽管我认为更重要的原因始终是减少复杂性)。


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