【问题标题】:How does delete[] "know" the size of the operand array?delete[] 如何“知道”操作数数组的大小?
【发布时间】:2010-09-16 21:29:10
【问题描述】:
Foo* set = new Foo[100];
// ...
delete [] set;

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

【问题讨论】:

  • sourceforge.net/projects/fastmm 是开源的,取代了内存管理器。在这里您可以了解内存管理的工作原理以及分配和删除内存的信息来自何处。
  • 请注意,FastMM 仅特定于 Delphi/C++Builder 编译器,它不是 C++ 的通用内存管理器。它甚至不是用 C++ 编写的。

标签: c++


【解决方案1】:

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

【讨论】:

  • 请注意,这只适用于 C++ 中的数组分配。所有其他分配都依赖于类型的大小。一些库确实存储所有分配大小,通常仅在调试模式下。
  • 程序员绝对没有理由不知道这些信息。我可以传递一个指向函数的指针并释放它,但是要在同一个函数中自己获取大小,我必须传递一个额外的参数。这有意义吗?
  • @Mark,它有一个 small 的意义,因为理论上它确实释放了分配器以始终存储 allocated 块的大小(可能与 requested 块的大小不同)。某些分配器设计可能出于自己的目的需要此信息,或者可能不够复杂,无法使用类型信息来跟踪非数组堆分配的大小等。强制分配器存储请求的大小(这样您就不会需要自己传递数组大小)可能是一个小负担,但它可能会对可想象的分配器设计产生性能影响。
  • 对不起,这个答案没有抓住重点。 QuantumPete 所描述的基本上是“free 如何知道要释放多少内存”。是的,内存块大小由malloc“某处”存储(通常在块本身中),这就是free 知道的方式。但是,new[]/delete[] 是另一回事。后者基本上工作在malloc/free之上。 new[] 还将它创建的元素的数量存储在内存块中(独立于 malloc),以便以后 delete[] 可以检索并使用该数量来调用适当数量的析构函数。
  • 即物理上两个计数器存储在块中:块大小(malloc)和元素计数(new[])。请注意,前者不能用于计算后者,因为在一般情况下,内存块的大小可能比请求大小的数组实际需要的大。另请注意,只有具有非平凡析构函数的类型才需要数组元素计数器。对于具有普通析构函数的类型,计数器不会由new[] 存储,当然,也不会由delete[] 检索。
【解决方案2】:

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

举例说明:

这里

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)

【讨论】:

  • 可以修改它以在运行时获取数组长度吗?
  • 是的,如果你想滥用 UB。例如你可以做ptr[-1].
【解决方案3】:

信息不规范。然而,在我处理过的平台中,这些信息存储在内存中,就在第一个元素之前。因此,理论上您可以访问并检查它,但这并不值得。

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

【讨论】:

    【解决方案4】:

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

    您可以通过意识到delete[] 仅为new[] 返回的指针定义,这可能与operator new[] 返回的指针不同,从而考虑可能的实现。一个实际的实现是将数组计数存储在operator new[] 返回的第一个 int 中,并让new[] 返回一个指针偏移量。 (这就是为什么非平凡的对齐会破坏new[]。)

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

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

    【讨论】:

      【解决方案5】:

      基本上它在内存中的排列为:

      [信息][你要求的内存...]

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

      这取决于实现。

      【讨论】:

        【解决方案6】:

        这不是规范中的内容——它依赖于实现。

        【讨论】:

          【解决方案7】:

          因为要“删除”的数组应该是用一次“new”运算符创建的。 “新”操作应该将该信息放在堆上。否则,new 的其他用途如何知道堆在哪里结束?

          【讨论】:

            【解决方案8】:

            这是一个比您最初想象的更有趣的问题。此回复是关于一种可能的实现方式。

            首先,虽然在某种程度上您的系统必须知道如何“释放”内存块,但底层的 malloc/free(通常称为 new/delete/new[]/delete[])并不总是能准确记住您要求多少内存,它可以向上取整(例如,一旦超过 4K,它通常会向上取整到下一个 4K 大小的块)。

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

            除了,如果被构造的类型没有析构函数,那么 delete[] 除了释放内存块之外不需要做任何事情,因此不需要存储任何东西!

            【讨论】:

              【解决方案9】:

              它没有标准化。在 Microsoft 的运行时中,new 运算符使用 malloc(),而 delete 运算符使用 free()。所以,在这种设置下,你的问题就相当于:free() 如何知道块的大小?

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

              【讨论】:

              • 不正确。在调用free之前,delete[]需要先调用析构函数。对于这个知道总分配大小是不够的。实际上 new[] 和 delete[] 对于 VC++ 中的普通类型和破坏类型的工作方式不同。
              猜你喜欢
              • 1970-01-01
              • 2011-04-04
              • 1970-01-01
              • 1970-01-01
              • 2015-01-15
              • 2015-01-21
              相关资源
              最近更新 更多