如何通过delete[]知道操作数组的大小?

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

在使用delete[]删除数组时,不要超出数组的边界。但是这些信息存储在哪里?是否有标准化?


http://sourceforge.net/projects/fastmm/ 是一个开源项目,它可以替换内存管理器。在这里,您可以了解内存管理的工作原理以及分配和释放内存所需的信息来源。 - user5598651
1
请注意,FastMM仅适用于Delphi/C++Builder编译器,它不是C++的通用内存管理器。它甚至不是用C++编写的。 - Remy Lebeau
9个回答

229

当你在堆上分配内存时,你的内存分配器会跟踪你已经分配了多少内存。这通常存储在一个“head”段中,就在你被分配的内存之前。因此,当释放内存的时候,解除分配器知道需要释放多少内存。


5
请注意,这仅适用于 C++ 中的数组分配。所有其他分配都依赖于类型的大小。一些库通常仅在调试模式下存储所有分配大小。 - Zan Lynx
112
程序员应该可以获得这些信息,没有任何理由不让他们获得。我可以传递一个指向函数的指针并释放它,但是要在同一个函数中获取大小却需要传递额外的参数。这有意义吗? - Mark Ruzon
32
@Mark,这有点道理,因为理论上它确实使分配器能够始终存储分配的块的大小(可能与请求的块的大小不同)。某些分配器设计可能需要此信息以用于其自身目的,或者可能不够复杂以使用类型信息来跟踪非数组堆分配的大小等。强制分配器存储请求的大小(以便您无需自己传递数组大小)可能是一个小负担,但可能会对可想象的分配器设计产生性能影响。 - Doug McClean
40
抱歉,但这个答案没有抓住重点。 QuantumPete所描述的基本上是“free如何知道要释放多少内存”。是的,内存块大小通常由malloc(通常在块本身中)存储在“某个地方”,因此这就是free如何知道的。然而,new[]/delete[]则是另一回事。后者基本上是在malloc/free之上运作的。 new[]还将其创建的元素数存储在内存块中(独立于malloc),以便稍后的delete[]可以检索并使用该数字来调用适当数量的析构函数。 - AnT stands with Russia
36
即在块中实际上存储了两个计数器:块的大小(通过malloc)和元素计数(通过new[])。需要注意的是,前者不能用于计算后者,因为在一般情况下,内存块的大小可能比所请求的数组所需的更大。还要注意,仅针对具有非平凡析构函数的类型才需要数组元素计数器。对于具有平凡析构函数的类型,计数器不会由new[]存储,并且当然不会由delete[]检索。 - AnT stands with Russia
显示剩余14条评论

35

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

以下是一个示例:

这里

int* i = new int[4];

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

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

将“4”存储在第一个sizeof(int)个字节中

*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)

2
这个能被修改以在运行时获取数组长度吗? - Jessie Lesbian
1
是的,如果你想滥用未定义行为。例如,你可以执行 ptr[-1] - user4945014

13

这些信息并没有标准化。然而,在我所工作的平台上,这些信息存储在第一个元素之前的内存中。因此理论上你可以访问并检查它,但这并不值得。

同时也正因为如此,当你使用new[]分配内存时,必须使用delete[]来释放内存,因为数组版本的delete知道(和在哪里)需要查找以释放正确数量的内存 - 并调用适当数量的对象析构函数。


8

这是由C++标准定义的,因此是特定于编译器的。这意味着它具有编译器魔法的性质。在至少一个主要平台上,它可能会受到非平凡对齐限制的影响而出现问题。

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

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

此外,这与C如何知道malloc分配的内存大小无关。


5
基本上,它在内存中的排列方式如下:

[信息][您请求的内存...]

其中,信息是编译器用于存储所分配内存数量等信息的结构体。

不过这取决于实现方式。


4

这不是规范中的内容,而是依赖于具体的实现。


2

因为要“删除”的数组应该是使用“new”运算符创建的。这个“new”操作应该将信息放在堆上。否则,其他使用new的操作怎么知道堆在哪里结束呢?


1

这个问题比你一开始想象的更有趣。这篇回复是关于可能的实现方式。

首先,在某个层面上,你的系统必须知道如何“释放”内存块,但底层的malloc/free(通常由new/delete/new[]/delete[]调用)并不总是记得准确地请求了多少内存,它可以被舍入(例如,一旦超过4K,它经常被舍入到下一个大小为4K的块)。

因此,即使能够获得内存块的大小,也不能告诉我们新分配的内存中有多少值,因为它可能更小。因此,我们必须存储一个额外的整数来告诉我们有多少个值。

除非正在构造的类型没有析构函数,否则delete[]除了释放内存块之外什么也不需要做,因此不需要存储任何东西!


-1

这并不是标准化的。在微软的运行时中,new操作符使用malloc()函数,而delete操作符使用free()函数。因此,在这种情况下,你的问题等同于以下问题:free()如何知道块的大小?

背后有一些书记工作,即在C运行时中进行。


7
不正确。在调用 free 之前,需要先调用 delete[] 析构函数。仅知道总分配大小是不够的。实际上,在 VC++ 中,针对普通类型和已析构类型,new[] 和 delete[] 的工作方式是不同的。 - Suma

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