动态分配数组大小的获取

3
在《C++编程语言》一书中,Stroustrup说:
“为了释放由new, delete, 和 delete[]分配的内存空间,必须能够确定所分配对象的大小。这意味着使用标准实现的new分配的对象所占用的空间略大于静态对象。通常,一个单词用来保存对象的大小。”
也就是说,每个由new分配的对象都有其大小位于堆上的某处。这个位置是否已知,如果是,我该如何访问它?

5
访问它没有标准方式。 - kennytm
1
请查看此答案中的评论:https://dev59.com/4HVC5IYBdhLWcg3wvT1a#197699。 - chris
@KennyTM 真正的问题是为什么。 - Elazar
@Elazar 因为如果您使用 new 分配对象数组,那么您已经必须在某处存储了该计数。 - user529758
@H2CO3 但是这个地方可能很遥远,由其他人实现,并且只能以二进制形式访问。 - Elazar
显示剩余2条评论
3个回答

7
实际上,内存分配器的典型实现还会存储一些其他信息。没有标准的方法来访问这些信息,事实上标准中也没有说明存储了哪些信息(如字节数、元素数量及其大小、最后一个元素的指针等)。编辑:如果您有对象的基地址和正确的类型,我认为可以相对容易地找到分配的大小(不一定“完全免费”)。但是,存在以下几个问题:
1.它假设您拥有原始指针。 2.它假设内存是使用特定运行时库的分配代码精确分配的。 3.它假设分配器没有以某种方式“舍入”分配地址。
为了说明这可能出错的情况,我们假设执行以下操作:
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!
}

任何实现都可以在技术上提供len()函数,而不需要任何成本,这是真的吗? - Elazar
但这意味着 int values[3]; 必须在堆栈上存储大量额外的值,因为编译器无法知道您是否将其传递给 len。根据 C++ 的原则“只为实际使用付费”,这是行不通的。 - Mats Petersson
编译器会知道:values是一个栈上的数组 - 会衰变成一个普通指针,对其调用len将无法在任何地方编译... 但我明白了。谢谢你,加一分 :) - Elazar
这仍然意味着编译器必须具有一些额外的信息,以知道不同类型指针之间的区别... 如果 get_len_array() 在不同的源文件中怎么办? - Mats Petersson
@MatsPetersson 谢谢,Mats。+1 确认这个额外的位。 - lapk
显示剩余10条评论

1
这意味着由new分配的每个对象的大小都位于堆中的某个位置。该位置是否已知,如果已知,我如何访问它?
并非所有情况都需要知道,为了简化推理,有两个级别可能需要知道大小。在语言级别上,编译器需要知道要销毁什么。在分配器级别上,分配器需要知道如何仅通过指针释放内存。
在语言级别上,仅数组版本new[]和delete[]需要处理任何大小。当您使用new进行分配时,您将获得具有对象类型的指针,而该类型具有给定的大小。
销毁对象时不需要大小。当您删除时,指针要么是正确的类型,要么静态指针类型是基类且析构函数是虚拟的。所有其他情况都是未定义行为,因此可以忽略(任何事情都可能发生)。如果是正确的类型,则已知大小。如果是具有虚拟析构函数的基类,则动态调度将找到最终覆盖者,并在那一点上确定类型。
有不同的策略来管理这个问题,在Itanium C++ ABI中使用的方法(在多个平台上由多个编译器使用,但不包括Visual Studio)例如为每种类型生成多达3个不同的析构函数之一,其中一个版本负责释放内存,因此尽管delete ptr是按照调用适当的析构函数然后释放内存来定义的,但在这个特定的ABI中,delete ptr调用了一个既销毁又释放内存的特殊析构函数。

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

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

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

当然,这些都是实现细节,在标准可移植的方式下不可访问C++程序,并且确切的实现不仅取决于程序,还取决于执行环境。如果您想知道信息在您的环境中如何真正保存,您可能能够找到信息,但我建议在尝试将其用于学习目的之外的任何事情之前三思。

0

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

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

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


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