delete []如何“知道”操作数数组的大小?


250
Foo* set = new Foo[100];
// ...
delete [] set;

您不会将数组的边界传递给delete[]。但是这些信息存储在哪里?它是标准化的吗?


sourceforge.net/projects/fastmm是开源的,它取代了内存管理器。在这里,您可以找到内存管理的工作方式以及分配和删除内存的信息来源。

1
请注意,FastMM仅特定于Delphi / C ++ Builder编译器,它不是C ++的通用内存管理器。它甚至不是用C ++编写的。
雷米·勒博

Answers:


181

在堆上分配内存时,分配器将跟踪已分配的内存量。这通常存储在分配内存之前的“ head”段中。这样,当需要释放内存时,解除分配器就会确切知道要释放多少内存。


4
请注意,这仅适用于C ++中的数组分配。所有其他分配取决于类型的大小。有些库确实存储所有分配大小,通常仅在调试模式下。
Zan Lynx

97
绝对没有理由不能让程序员使用此信息。我可以传递一个指向函数的指针并释放它,但是要在同一函数中获取自己的大小,我必须传递一个额外的参数。这有任何意义吗?
Mark Ruzon 09年

26
@Mark,它意义不大,因为从理论上讲,它确实释放了分配器以始终存储分配的块的大小(可能与请求的块的大小不同)。某些分配器设计可能出于自身目的需要此信息,或者可能不够复杂,无法使用类型信息来跟踪非数组堆分配的大小,等等。强制分配器存储请求的大小(这样您就不会需要自己传递数组大小)可能会有点麻烦,但可能会对可能的分配器设计产生性能影响。
道格·麦克林

33
对不起,但是这个答案没有讲到重点。QuantumPete所说的基本上是“如何free知道要释放多少内存”。是的,内存块大小是“某处”存储的malloc(通常存储在该块本身中),因此可以free知道。但是,new[]/ delete[]是一个不同的故事。后者基本上在malloc/ 之上工作freenew[]还将创建的元素数量存储在内存块中(与无关malloc),以便以后delete[]可以检索并使用该数量来调用适当数量的析构函数。
AnT

26
也就是说,物理上两个计数器存储在块中:块大小(按malloc)和元素计数(按new[])。注意,前者不能用于计算后者,因为在一般情况下,存储块的大小可能大于请求的大小数组的实际所需大小。还要注意,只有具有非平凡析构函数的类型才需要使用数组元素计数器。对于具有琐碎析构函数的类型,计数器不会由存储new[],当然也不会由检索delete[]
AnT

23

编译器的一种方法是分配更多的内存,并将一定数量的元素存储在head元素中。

示例如何完成:

这里

int* i = new int[4];

编译器将分配sizeof(int)*5字节。

int *temp = malloc(sizeof(int)*5)

将在前sizeof(int)几个字节中存储“ 4”

*temp = 4;

并设置 i

i = temp + 1;

因此i将指向一个由4个元素而不是5个元素组成的数组。

和删除

delete[] i;

将以以下方式处理:

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements
... that are stored in temp + 1, temp + 2, ... temp + 4 if needed
free (temp)

9

该信息未标准化。但是,在我处理过的平台上,该信息存储在内存中的第一个元素之前。因此,从理论上讲,您可以访问它并对其进行检查,但这是不值得的。

这也是为什么在使用new []分配内存时必须使用delete []的原因,因为delete的数组版本知道(需要在何处)释放适当数量的内存-并调用适当数量的析构函数对于对象。


5

其在内存中的排列基本上是:

[info] [您要求的备忘录...]

其中info是编译器用来存储分配的内存量的结构,而不是存储内存的结构。

但是,这取决于实现。


4

这不是规范中的内容-它取决于实现。


3

它在C ++标准中定义为特定于编译器。这意味着编译器魔术。在至少一个主要平台上,它可能会因非平凡的对齐限制而中断。

您可以通过意识到delete[]仅为返回的指针定义的实现来考虑可能的实现,该指针new[]可能与返回的指针不同operator new[]。常见的一种实现方式是将数组计数存储在由返回的第一个int中operator new[],并new[]返回超出该值的指针偏移量。(这就是为什么非平凡的对齐方式可能会中断的原因new[]。)

请记住operator new[]/operator delete[]!= new[]/delete[]

另外,这与C如何知道由分配的内存大小正交malloc


2

因为要被“删除”的数组应该已经使用“ new”运算符创建了。“新”操作应该已将该信息放在堆上。否则,如何使用new来知道堆在哪里结束?


0

它不是标准化的。在Microsoft的运行时中,new运算符使用malloc(),delete运算符使用free()。因此,在这种情况下,您的问题等同于以下内容:free()如何知道块的大小?

在幕后,即在C运行时,进行了一些簿记。


5
不对。在调用free之前,delete []需要首先调用析构函数。为此,仅知道总分配大小是不够的。实际上,new []和delete []对于VC ++中的普通类型和销毁类型都起作用。
苏马

0

这是一个比您一开始可能想到的有趣的问题。此答复是关于一种可能的实现。

首先,虽然您的系统必须在某种程度上知道如何“释放”内存块,但底层的malloc / free(通常会调用new / delete / new [] / delete [])并不总是记住确切的内存量您要求将其四舍五入(例如,一旦高于4K,通常会将其四舍五入到下一个4K大小的块)。

因此,即使可以获得内存块的大小,也不会告诉我们new [] ed内存中有多少个值,因为它可以更小。因此,我们必须存储一个额外的整数,告诉我们有多少个值。

除此以外,如果正在构造的类型没有析构函数,那么delete []除了释放内存块外无需执行任何操作,因此也不需要存储任何内容!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.