【问题标题】:Does Ada deallocate memory automatically under some circumstances?Ada 在某些情况下会自动释放内存吗?
【发布时间】:2021-07-11 21:00:56
【问题描述】:

我试图找到一些关于为什么关键字new 可用于动态分配对象的信息,但没有像delete 这样的关键字可用于释放它们。通过Ada 2012 参考手册中提到的Ada.Unchecked_Deallocation,我发现了一些有趣的摘录:

每个对象在被销毁之前都已完成(例如,通过 留下一个包含 object_declaration 的 subprogram_body,或者通过调用 Unchecked_Deallocation)

每个对象访问类型都有一个关联的存储池。分配器分配的存储空间来自 从游泳池; Unchecked_Deallocation 实例将存储返回到池中。

用户定义的存储池对象 P 的 Deallocate 过程可以由实现调用以 仅在允许对 P 进行分配调用的位置为池为 P 的类型 T 取消分配存储, 在执行 T 的 Unchecked_Deallocation 实例期间,或作为最终确定的一部分 T的集合。

如果我不得不猜测,这意味着当执行离开 access 的范围时,实现可以自动释放与 access 关联的对象em> 被声明。无需显式调用Unchecked_Deallocation

a section in Ada 95 Quality and Style Guide 似乎支持这一点:

未经检查的存储释放机制是一种覆盖回收分配存储的默认时间的方法。最早的默认时间是对象不再可访问的时间,例如,当控制离开声明访问类型的范围时(此时间之后的确切时间取决于实现)。如果尝试访问该对象,则在此之前执行的任何未经检查的存储释放都可能导致错误的 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;

Run 完成时,是否有保证的内存泄漏或X 被释放?

【问题讨论】:

    标签: memory-management ada


    【解决方案1】:

    Memory Management with Ada 2012 中所述,引用here,局部变量通常分配在堆栈上;当变量的作用域退出时,它的内存会自动释放。相反,动态变量通常分配在上;它的内存是使用new分配的,它的内存必须被回收,通常:

    • 明确地,例如使用Unchecked_Deallocation 的实例。

    • 隐式,例如使用派生自Finalization 的受控类型;如here 所述,当受控实例的范围退出时,自动终结调用Finalize,它以适合类型设计的方式回收存储。

    Ada.Containers 的子代在内部使用受控类型来封装访问值并自动管理内存。作为参考,请将特定容器的编译器实现与引用 here 的相应功能容器进行比较。

    Ada 提供了多种管理内存的方法,幻灯片 28 上按作者的偏好顺序进行了总结:

    1. 基于堆栈。
    2. 基于容器。
    3. 基于最终确定。
    4. 基于子池。
    5. 手动分配/解除分配。

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

    应在该类型的分配器处创建匿名访问类型的存储池,并在指定对象变得不可访问时回收。

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

    【讨论】:

    • 作者本人explains slide 12 并声称当访问类型超出范围时可能会回收内存。根据ARM (18/4),它实际上是指定 Storage_Size 的访问类型所必需的。我不认为程序会泄漏 16 个整数的说法是完全正确的。
    • @GDI512:是的;一个实现是允许——但不是必需——在你的例子中回收内存;我试图在上面澄清。
    • 对,我想我现在明白了。但是命名访问类型呢?我在 ARM 中找不到任何强有力的保证,所以我假设同样的规则适用,但也许我还没有深入了解它。当访问类型超出范围时,标准或 GNAT 是否有任何强有力的保证? (添加了另一个例子来说明我的意思)
    • @trashgod:我觉得说 Finalization 实现了隐式释放有点误导。当受控类型的对象被“销毁”时,终结实现对 Finalize 过程的隐式调用,这通常发生在堆栈分配的受控对象的范围离开时。但是如果此时应该释放一些堆分配的内存,Finalize 过程必须显式地释放它,通常使用 Unchecked_Deallocation。
    • @NiklasHolsti:同意;我已经更新了答案以反映这一点。
    【解决方案2】:

    您的程序会泄漏内存吗?这取决于编译器。

    AFAIK,只有两次需要编译器来回收分配的内存:

    1. 当指定Storage_Size 的访问类型超出范围时
    2. Ada.Unchecked_Deallocation 的实例以非空值调用时

    但是,编译器允许在其他情况下回收内存。例如,编译器可能会实现垃圾回收,但我不知道有什么实现。

    FWIW,我不知道你的程序有哪些编译器不会泄漏内存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 2016-12-09
      • 1970-01-01
      • 2021-01-02
      • 1970-01-01
      • 2014-08-16
      • 1970-01-01
      相关资源
      最近更新 更多