【问题标题】:What is the equivalent of placement new in Ada?什么相当于 Ada 中的新安置?
【发布时间】:2021-03-20 02:33:00
【问题描述】:

我正在尝试通过编写一组用于管理动态数组的过程来学习 Ada,但我不知道如何去做。在 C++ 中,我可以轻松地将任意对象放入内存中,如下所示:

#include <new>
#include <iostream>

class object {
  private:
    int value;

  public:
    ~object() noexcept { std::cout << "<> "; }
    object(int value) noexcept : value(value) { std::cout << object::value << ' '; }
};

constexpr auto size = 16;

int main() {
    auto buffer = static_cast<object*>(::operator new(size * sizeof(object)));
    for (auto offset = 0; offset < size; offset++)
        new (buffer + offset) object(offset);
    for (auto offset = 0; offset < size; offset++)
        (buffer + offset)->~object();
    ::operator delete(buffer);
}

在 Ada 中是否有类似“分配 x * sizeof(y) 并初始化 x y-items”的操作?

【问题讨论】:

  • Nitpick:如果你想学习如何用编程语言做某件事,通常最好用语言描述你想做的事情,而不是用另一种语言的代码来描述对可能回答您问题的人来说很熟悉。
  • 您的主代码似乎分配了一个足够容纳 16 个“对象”的缓冲区,然后将该缓冲区的各个部分分配为单独的对象,将缓冲区用作内存池。在 Ada 中,我要么使用“存储池”概念,要么只分配一个类型为“对象数组(1..16)”的“对象池”。如果你更清楚地解释你想做什么,也许我可以更有帮助地回答。
  • 在 Ada 中,这些常见的use cases for "placement new" 最好单独处理。
  • @NiklasHolsti 对不起,我应该用不同的措辞。我的意思是提前分配一个内存缓冲区,然后将可能不同类型的对象一个接一个地放入其中,就像在 C++ 中使用指针算术和放置 new 一样。
  • 是的,但您为什么要这样做而不是仅仅使用new?我认为Maxim’s answer 可能是最接近的 - 但要注意对齐问题

标签: c++ memory-management ada


【解决方案1】:

在 Ada 中,您可以编写自定义“storage pool”(或子池) 您在其中重新定义分配函数以使用您的缓冲区。然后你将访问类型“绑定”到你的存储池对象。

with System.Storage_Pools;
with System.Storage_Elements;

procedure Main is
   type My_Storage_Pool is new System.Storage_Pools.Root_Storage_Pool with record
      Buffer : System.Storage_Elements.Storage_Array (1 .. 1024);
      Offset : System.Storage_Elements.Storage_Count := 1;
   end record;

   overriding procedure Allocate
     (Self      : in out My_Storage_Pool;
      Address   : out System.Address;
      Size      : System.Storage_Elements.Storage_Count;
      Alignment : System.Storage_Elements.Storage_Count);
   
   overriding procedure Deallocate
     (Self      : in out My_Storage_Pool;
      Address   : System.Address;
      Size      : System.Storage_Elements.Storage_Count;
      Alignment : System.Storage_Elements.Storage_Count) is null;

   overriding function Storage_Size
     (Self : My_Storage_Pool)
      return System.Storage_Elements.Storage_Count is (Self.Buffer'Length);
   
   procedure Allocate
     (Self      : in out My_Storage_Pool;
      Address   : out System.Address;
      Size      : System.Storage_Elements.Storage_Count;
      Alignment : System.Storage_Elements.Storage_Count)
   is
      use type System.Storage_Elements.Storage_Count;
   begin
      Address := Self.Buffer (Self.Offset)'Address;
      Self.Offset := Self.Offset + Size;
   end Allocate;
   
   Pool : My_Storage_Pool;
   
   type Object is null record;
   type Object_Access is access Object with Storage_Pool => Pool;

   X : Object_Access := new Object;
begin
   null;
end Main;

另外,对于 untagged 类型,您可以使用Address 子句将对象放置到给定地址。这要简单得多。但是这种方式对象初始化是行不通的,所以 tagged 类型的对象不会得到虚拟表指针:

Buffer : Stream_Element_Array (1..1024);
Object : My_Object_Type
  with Import, Address => Buffer (Offset)'Address;
--  Here Object'Tag is unassigned and dispatching won't work

【讨论】:

  • Address-clause 方法对于未标记的类型也有问题:由于没有发生正常的初始化,组件将不会获得它们的初始默认值。
【解决方案2】:

是的。 Ada 确实允许动态内存分配。分配器的主题在section 4.8 of the Ada Reference Manual 中处理。参考手册中的示例如下:

16 分配器示例: 17 new Cell'(0, null, null) -- 显式初始化,见 3.10.1 new Cell'(Value => 0, Succ => null, Pred => null) -- 显式初始化 新单元——未初始化 18 new Matrix(1 .. 10, 1 .. 20) - 仅给出边界 new Matrix'(1 .. 10 => (1 .. 20 => 0.0)) -- 显式初始化 19 new Buffer(100) -- 只给出判别式 new Buffer'(Size => 80, Pos => 0, Value => (1 .. 80 => 'A')) -- 显式初始化

你必须先声明要分配的类型,然后是访问分配内存的访问类型。

以下示例对整数数组执行并行加法。数组是动态分配的,因为使用分配器可以分配比在堆栈上分配更大的数组。

包规范定义了数组类型和访问类型。

package Parallel_Addition is
   type Data_Array is array(Integer range <>) of Integer;
   type Data_Access is access all Data_Array;
   function Sum(Item : in not null Data_Access) return Integer;
end Parallel_Addition;

包体使用两个任务对数组的内容求和。

package body Parallel_Addition is

   ---------
   -- Sum --
   ---------

   function Sum (Item : in not null Data_Access) return Integer is
      task type Adder is
         entry Set (Min : Integer; Max : Integer);
         entry Report (Value : out Integer);
      end Adder;

      task body Adder is
         Total : Integer := 0;
         First : Integer;
         Last  : Integer;
      begin
         accept Set (Min : Integer; Max : Integer) do
            First := Min;
            Last  := Max;
         end Set;
         for I in First .. Last loop
            Total := Total + Item (I);
         end loop;
         accept Report (Value : out Integer) do
            Value := Total;
         end Report;
      end Adder;
      A1  : Adder;
      A2  : Adder;
      R1  : Integer;
      R2  : Integer;
      Mid : constant Integer := (Item'Length / 2) + Item'First;
   begin
      A1.Set (Min => Item'First, Max => Mid);
      A2.Set (Min => Mid + 1, Max => Item'Last);
      A1.Report (R1);
      A2.Report (R2);
      return R1 + R2;
   end Sum;

end Parallel_Addition;

正如你在上面看到的,数组元素的访问不需要特殊的语法来通过访问类型取消引用数组。该程序的“主要”程序是

with Parallel_Addition; use Parallel_Addition;
with Ada.Text_IO;       use Ada.Text_IO;
with Ada.Calendar;      use Ada.Calendar;

procedure Parallel_Addition_Test is
   The_Data : Data_Access := new Data_Array (1 .. Integer'Last);
   Start    : Time;
   Stop     : Time;
   The_Sum  : Integer;

begin
   The_Data.all := (others => 1);
   Start        := Clock;
   The_Sum      := Sum (The_Data);
   Stop         := Clock;
   Put_Line ("The sum is: " & Integer'Image (The_Sum));
   Put_Line
     ("Addition elapsed time is " &
      Duration'Image (Stop - Start) &
        " seconds.");
   Put_Line
     ("Time per addition operation is " &
        Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &
        " seconds.");
end Parallel_Addition_Test;

这个程序在我的 Windows 10 电脑上运行时的输出是:

The sum is:  2147483647
Addition elapsed time is  5.628780600 seconds.
Time per addition operation is  2.62111E-09 seconds.

【讨论】:

  • 这个答案虽然准确且被接受,但似乎并没有解决问题; 'placement new' 显然与普通的 'new' 不同。
  • @SimonWright 对。我接受了它,因为它回答了我当时的一些关于 Ada 的问题。我也忘记了这个问题在这个过程中是关于什么的......我将把它取消标记为已接受,以便让它对其他人有用,因为这个主题是关于安置新的和安置新的等价物。
猜你喜欢
  • 2015-03-28
  • 1970-01-01
  • 2018-12-15
  • 2020-04-26
  • 2019-09-12
  • 2020-02-28
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多