获取动态分配的数组大小
Posted
技术标签:
【中文标题】获取动态分配的数组大小【英文标题】:Getting dynamically allocated array size 【发布时间】:2013-09-05 21:11:27 【问题描述】:在《C++ 编程语言》一书中,Stroustrup 说:
“要释放由 new 分配的空间,delete 和 delete[] 必须能够确定分配的对象的大小。这意味着使用 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:见我上面的编辑。不,这不是一件小事。
这与delete
或free()
的问题相同。
一般来说,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 来指向另一个新分配
【讨论】:
以上是关于获取动态分配的数组大小的主要内容,如果未能解决你的问题,请参考以下文章