这意味着由new分配的每个对象的大小都位于堆中的某个位置。该位置是否已知,如果已知,我如何访问它?
并非所有情况都需要知道,为了简化推理,有两个级别可能需要知道大小。在语言级别上,编译器需要知道要销毁什么。在分配器级别上,分配器需要知道如何仅通过指针释放内存。
在语言级别上,仅数组版本new[]和delete[]需要处理任何大小。当您使用new进行分配时,您将获得具有对象类型的指针,而该类型具有给定的大小。
销毁对象时不需要大小。当您删除时,指针要么是正确的类型,要么静态指针类型是基类且析构函数是虚拟的。所有其他情况都是未定义行为,因此可以忽略(任何事情都可能发生)。如果是正确的类型,则已知大小。如果是具有虚拟析构函数的基类,则动态调度将找到最终覆盖者,并在那一点上确定类型。
有不同的策略来管理这个问题,在Itanium C++ ABI中使用的方法(在多个平台上由多个编译器使用,但不包括Visual Studio)例如为每种类型生成多达3个不同的析构函数之一,其中一个版本负责释放内存,因此尽管
delete ptr
是按照调用适当的析构函数然后释放内存来定义的,但在这个特定的ABI中,
delete ptr
调用了一个既销毁又释放内存的特殊析构函数。
当您使用new[]
时,指针的类型与动态数组中元素的数量无关,因此类型不能用于检索该信息。常见的实现方法是分配一个额外的整数值并将大小存储在那里,接着是真正的对象,然后返回第一个对象的指针。delete[]
然后会将接收到的指针向后移动一个整数,并读取元素的数量,为所有元素调用析构函数,然后释放内存(由分配器检索的指针,而不是程序给出的指针)。如果类型具有非平凡的析构函数,则确实需要这样做;如果类型具有平凡的析构函数,则实现不需要存储大小,并且您可以避免存储该数字。
在语言级别之外,真正的内存分配器(类似于malloc
)需要知道分配了多少内存,以便可以释放相同数量的内存。在某些情况下,可以通过将元数据附加到内存缓冲区中来完成,就像new[]
存储数组大小一样,通过获取较大的块,在那里存储元数据并返回指针超过它。然后,解分配器将撤消转换以到达元数据。
另一方面,并非总是需要这样做。小尺寸分配器的常见实现是分配内存页面以形成池,从中获取小的分配。为了使其有效,分配器仅考虑几个不同的大小,并且不适合其中任何一个大小的分配都会被推到下一个大小。例如,如果您请求65字节,则分配器实际上可能会给您128字节(假设64和128字节的池)。因此,给定由分配器管理的较大块,从中分配的所有指针都具有相同的大小。然后,分配器可以找到分配指针的块并从中推断出大小。
当然,这些都是实现细节,在标准可移植的方式下不可访问C++程序,并且确切的实现不仅取决于程序,还取决于执行环境。如果您想知道信息在您的环境中如何真正保存,您可能能够找到信息,但我建议在尝试将其用于学习目的之外的任何事情之前三思。
new
分配对象数组,那么您已经必须在某处存储了该计数。 - user529758