获取动态分配的数组大小

Posted

技术标签:

【中文标题】获取动态分配的数组大小【英文标题】:Getting dynamically allocated array size 【发布时间】:2013-09-05 21:11:27 【问题描述】:

在《C++ 编程语言》一书中,Stroustrup 说:

“要释放由 new 分配的空间,deletedelete[] 必须能够确定分配的对象的大小。这意味着使用 new 的标准实现分配的对象将占用比静态对象稍多的空间。通常,使用一个词来保存对象的大小。

这意味着new 分配的每个对象的大小都位于堆中的某个位置。该位置是否已知,如果已知,我如何访问它?

【问题讨论】:

没有标准的访问方式。 查看this answer中的cmets。 @KennyTM 真正的问题是为什么。 @Elazar 因为如果您使用 new 分配对象数组,您已经必须将该计数存储在某处。 @H2CO3 但这个地方可能很远,由其他人实现,只能以二进制形式访问。 【参考方案1】:

这意味着由 new 分配的每个对象的大小都位于堆中的某个位置。该位置是否已知,如果已知,我如何访问它?

并非如此,所有情况都不需要。为了简化推理,可能需要两个级别的大小。在语言级别,编译器需要知道要销毁什么。在分配器级别,分配器需要知道如何释放仅给定指针的内存。

在语言级别,只有数组版本new[]delete[] 需要处理任何大小。当您使用new 进行分配时,您会得到一个带有对象类型的指针,并且该类型具有给定的大小。

要销毁不需要大小的对象。当您delete 时,要么指针指向正确的类型,要么指针的静态类型是基类而析构函数是虚拟的。所有其他情况都是未定义的行为,因此可以忽略(任何事情都可能发生)。如果它是正确的类型,那么大小是已知的。如果它是带有虚拟析构函数的基类,则动态调度将找到最终的覆盖器,并且此时类型是已知的。

可能有不同的策略来管理它,例如在 Itanium C++ ABI 中使用的策略(由多个平台中的多个编译器使用,尽管不是 Visual Studio)例如为每种类型生成多达 3 个不同的析构函数,其中一个是负责释放内存的版本,因此尽管delete ptr 是根据调用适当的析构函数然后释放内存来定义的,但在这个特定的 ABI 中delete ptr 调用了一个特殊的析构函数,它既可以销毁又可以释放内存。

当您使用new[] 时,无论动态数组中有多少元素,指针的类型都是相同的,因此无法使用该类型来检索该信息。一个常见的实现是分配一个额外的整数值并在那里存储大小,然后是真实对象,然后返回一个指向第一个对象的指针。 delete[] 然后将接收到的指针向后移动一个整数,读取元素的数量,为所有元素调用析构函数,然后释放内存(由分配器检索的指针,而不是给程序的指针)。仅当类型具有非平凡析构函数时才需要这样做,如果类型具有平凡析构函数,则实现不需要存储大小,您可以避免存储该数字。

在语言级别之外,真正的内存分配器(想想malloc)需要知道分配了多少内存,以便可以释放相同数量的内存。在某些情况下,可以通过以与new[] 存储数组大小相同的方式将元数据附加到内存缓冲区来完成,方法是获取更大的块,将元数据存储在那里并返回超出它的指针。然后释放器将撤消转换以获取元数据。

另一方面,这并不总是需要的。小尺寸分配器的常见实现是分配内存页面以形成池,然后从中获得小分配。为了提高效率,分配器只考虑几个不同的大小,并且不完全适合其中一个大小的分配被碰撞到下一个大小。例如,如果您请求 65 字节,分配器实际上可能会给您 128 字节(假设池为 64 和 128 字节)。因此,给定分配器管理的较大块之一,从它分配的所有指针都具有相同的大小。然后分配器可以找到从中分配指针的块并从中推断出大小。

当然,这都是 C++ 程序无法以标准可移植方式访问的所有实现细节,并且确切的实现不仅会因程序而异,还会因执行环境而异。如果您有兴趣了解这些信息是如何真正保存在您的环境中的,您也许可以找到这些信息,但在尝试将其用于学习以外的任何用途之前,我会三思而后行。

【讨论】:

【参考方案2】:

实际上,内存分配器的典型实现还存储了一些其他信息。

没有标准的方法来访问这些信息,实际上标准中也没有说明存储了什么信息(以字节为单位的大小、元素的数量及其大小、指向最后一个元素的指针等)。

编辑: 如果你有对象的基地址和正确的类型,我怀疑分配的大小可以相对容易地找到(不一定“完全免费”)。但是,有几个问题:

    假定您拥有原始指针。 它假定内存是使用该运行时库的分配代码准确分配的。 它假定分配器不会以某种方式“四舍五入”分配地址。

为了说明这可能会出错,假设我们这样做:

size_t get_len_array(int *mem)

   return allcoated_length(mem);


... 
void func()

    int *p = new int[100];
    cout << get_len_array(p); 
    delete [] p;


void func2()

    int buf[100];
    cout << get_len_array(buf); // Ouch!

【讨论】:

any 实现是否可以在技术上免费提供len() 函数? @Elazar:见我上面的编辑。不,这不是一件小事。 这与deletefree() 的问题相同。 一般来说,a-pointer-to-the-heap 应该是 a-pointer-to-the-stack 的子类型。 @Elazar 不确定“子类型”是什么意思...一般来说,代码不必关心指针的来源。如果您需要知道它们所指向的东西有多大的“指针”,请使用vector 或类似的东西。【参考方案3】:

您不是直接删除对象,而是发送指向delete 运算符的指针。 参考 C++ 您通过以下方式使用删除 它带有一个指向最初用 new 分配的内存块的指针:

int * ps = new int; // allocate memory with new
           . . .  // use the memory
delete ps;          // free memory with delete when done

这会删除 ps 指向的内存;它不会删除指针 ps 本身。 例如,您可以重用 ps 来指向另一个新分配

【讨论】:

以上是关于获取动态分配的数组大小的主要内容,如果未能解决你的问题,请参考以下文章

C++ 如何从动态分配中获取数组大小?

具有大小动态分配和预定大小的简单数组内存分配

如何找出动态分配数组的大小(使用 sizeof())? [复制]

如何调整动态分配的多态对象数组的大小?

如何调整类中动态分配的数组的大小?

如何在 C 中找到动态分配数组的大小?