在某些情况下,Ada是否会自动释放内存?

6
我试图找到一些关于为什么关键字new可以用于动态分配对象,但没有像delete这样的关键字可以用于销毁它们的信息。在阅读《Ada 2012参考手册》中有关Ada.Unchecked_Deallocation的提及时,我发现了一些有趣的摘录:

每个对象在被销毁之前都会被完成(例如通过离开包含对象声明的子程序体或调用Unchecked_Deallocation实例来实现)。

每个访问对象类型都有一个关联的存储池。分配器分配的存储来自该池; Unchecked_Deallocation的实例会返回存储到池中。

P用户定义的存储池对象的Deallocate过程可能会被实现调用,仅在允许P的Allocate调用的位置上为其池分配T类型的存储,在执行T的Unchecked_Deallocation实例期间,或作为集合T的终止的一部分来释放存储。

如果我要猜测,那就意味着当执行离开声明access的作用域时,与access相关联的对象可以被实现自动释放。无需显式调用Unchecked_Deallocation

这似乎得到了Ada 95质量和风格指南中的一节的支持,其中说明:

未经检查的存储器释放机制是覆盖回收分配存储器的默认时间的一种方法。最早的默认时间是当对象不再可访问时,例如,当控件离开访问类型声明作用域时(此后时间点依赖于实现)。在此之前执行任何未经检查的存储器释放可能会导致Ada程序错误,如果尝试访问对象。

但是措辞比较不清楚。如果我要运行这段代码,内存方面会发生什么事情呢?

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   procedure Run is
      X : access Integer := new Integer'(64);
   begin
      Put (Integer'Image (X.all));
   end Run;
begin
   for I in 1 .. 16 loop
      Run;
   end loop;
end Main;

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   procedure Outer is
      type Integer_Access is not null access Integer;
      procedure Run is
         Y : Integer_Access := new Integer'(64);
      begin
         Put (Integer'Image (Y.all));
      end Run;
   begin
      for I in 1 .. 16 loop
         Run;
      end loop;
   end Outer;
begin
   Outer;
end Main;

是否有内存泄漏的保证,还是XRun完成时被释放?

2个回答

9
使用Ada 2012进行内存管理 中所述,在这里引用,局部变量通常分配在上;当变量的作用域结束时,其内存会自动释放。相比之下,动态变量通常分配在上;它的内存使用new进行分配,并且其内存必须被回收,通常有以下两种方式:
  • 显式地,例如使用Unchecked_Deallocation的实例。

  • 隐式地,例如使用从Finalization派生的受控类型;如此处所述,当受控实例的作用域退出时,自动完成调用Finalize,以适合类型设计的方式回收存储。

Ada.Containers的子程序在内部使用受控类型来封装访问值并自动管理内存。为了参考,可以将编译器对特定容器的实现与此处引用的相应函数式容器进行比较。

Ada提供了多种管理内存的方法,在作者的首选顺序下,总结于幻灯片28中:

  1. 基于栈。
  2. 基于容器。
  3. 基于完成处理。
  4. 基于子池。
  5. 手动分配/释放。

在特定情况下的Main程序中,该程序为16个Integer实例分配存储空间。如幻灯片12所述,“编译器可能会在相应的访问类型超出作用域时回收已分配的内存。”例如,最近版本的GNAT参考手册指出,遵循以下存储管理实施建议:

匿名访问类型的存储池应在类型的分配器点上创建,并在指定对象变得无法访问时被回收。

如果没有这样的指示,存储器不需要被回收。当程序退出时,通常由主机操作系统来回收存储器。

1
作者本人在此处解释了幻灯片12,并声称当访问类型超出范围时,内存可以被回收。根据ARM(18/4)的规定,对于指定Storage_Size的访问类型实际上是必需的。我认为这个程序将泄漏16个整数的说法并不完全正确。 - GDI512
@GDI512:是的,实现是被允许的——但不是必须的——在你的例子中回收内存;我已经尝试在上面澄清了。 - trashgod
好的,我现在明白了。但是命名访问类型呢?我在ARM中没有找到任何强有力的保证,所以我认为相同的规则适用,但也许我还没有深入研究。当访问类型超出范围时,标准或GNAT是否有任何强有力的保证?(添加另一个示例以说明我的意思) - GDI512
@trashgod:我觉得说Finalization实现了隐式回收有些误导。Finalization实现了在受控类型的对象“销毁”时(通常发生在离开堆栈分配的受控对象的作用域时)对Finalize过程的隐式调用。但如果此时应该释放一些堆内存,则必须显式地使用Unchecked_Deallocation来释放它。 - Niklas Holsti
@NiklasHolsti:同意;我已经更新了答案以反映这一点。 - trashgod
@GDI512:Ada 受控类型具有“一个Finalize过程,该过程在受控对象的任何组件最终化之前立即调用。”更多信息请参见上文。 - trashgod

3
你的程序是否泄漏内存取决于编译器。
据我所知,只有两种情况下编译器才必须回收已分配的内存: 1. 当指定了 Storage_Size 的访问类型超出作用域时 2. 当一个非空值被传入 Ada.Unchecked_Deallocation 的实例中时
然而,在其他情况下编译器是可以回收内存的,例如编译器可能实现垃圾回收,但我不知道有哪些编译器这样做。
至于我所知,没有一种编译器能够使你的程序不泄漏内存。

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