艾达Ada:派生记录,其中元素与父元素重叠

问题描述

我正在尝试为两个密切相关的不同设备定义一个硬件接口,但是其中一个功能要比另一个更强。

作为问题的非常简化的版本,我们说:

  • 设备A有2个寄存器:R0未使用,R1已使用
  • 设备B有2个寄存器:同时使用R0和R1

我试图通过多态定义来重用代码和定义,如:

   Tag_Length : constant := Standard'Address_Size / System.Storage_Unit;

   type A_IO is tagged record
      --  RO precedes,is unused but the space is reserved
      R1 : Byte;
   end record;
   for A_IO use record
      R1 at Tag_Length + 1 range 0 .. 7;
   end record;
   
   type B_IO is new A_IO with record
      R0 : Byte;
      --  R1 would follow as defined by the parent record
   end record;
   for B_IO use record
      R0 at Tag_Length + 0 range 0 .. 7;
   end record;

这会导致在大多数情况下有意义的编译器错误component overlaps parent field of "B_IO"(GNAT社区2019)。

我对此有其他选择,其中包括

  • 为每个设备使用相同的类型( con:设备A将看到不可见的组件)
  • 使用完全不同的类型,在使用共享代码时,通过访问类型依赖于Unchecked Conversion来更改对象的视图( con:将涉及多次重新定义某些相同的组件)

我想知道如果没有任何上述缺点,是否有可行的方法

解决方法

我不知道那是不是bug。在7.1和7.2系列中,它可以很好地编译,但是在8.2和9.1中,它不能编译。

由于您愿意使用带标签的记录并让标签在位布局中占用空间,因此一种潜在的解决方法是使用不带标签的变体记录,并用变体替换标签。考虑:

type Record_Select is (A_IO,B_IO);
type Shared_Record(S : Record_Select) is record
    R1 : Byte;
    case S is
        when A_IO => null;
        when B_IO => R0 : Byte;
    end case; 
end record;
for Shared_Record use record
    S  at 0 range 0 .. 15;
    R0 at 2 range 0 .. 7;
    R1 at 3 range 0 .. 7;
end record;
for Shared_Record'Size use 32;

您可以调整大小以匹配您的实际标签大小。我只是输入了一些值。这将为您提供与标记记录类似的布局(当然要减去大小差异)。

此外,如果您将variant参数设置为具有默认值,则可以在两个变量之间进行复制,而无需进行未经检查的转换,只要您在类型中没有变量约束的情况下定义它们即可。

   type Record_Select is (A_IO,B_IO);

   -- Note the default value for S
   type Shared_Record(S : Record_Select := B_IO) is record
      R1 : Byte;
      case S is
         when A_IO => null;
         when B_IO => R0 : Byte;
      end case; 
   end record;
   for Shared_Record use record
      S  at 0 range 0 .. 15;
      R0 at 2 range 0 .. 7;
      R1 at 3 range 0 .. 7;
   end record;
   for Shared_Record'Size use 32;

   -- Note the unconstrained type definitions
   A : Shared_Record := (S => A_IO,R1 => 3);
   B : Shared_Record := (S => B_IO,R0 => 1,R1 => 2);
begin
   Put_Line(B.R1'Image);
   B := A;
   Put_Line(B.R1'Image);

输出:

 2
 3
,

以下是一些从Jere's answer汲取灵感并以此为基础的方法:

使用访问类型限制视图

  • 就像Jere一样,它使用一条记录,该记录将与依赖于变体的两个实现共享,以定义重叠字段。
  • 使用Unchecked_Union方面来消除存储变体的需要。
  • 将硬件IO定义封装在带标记的记录中,从而允许在两个设备的实现之间进行继承和OO。不管某种形式的设备的软件实现需要保持内部状态,而不仅仅是IO结构,都需要某种封装。
  • 使用访问类型来确保每个设备实现只能访问正确的组件(无需依赖未经检查的转换)。
   package Devices is
      type Record_Select is (A_IO,B_IO);
      type Shared_IO (S : Record_Select) is record
         R1 : Byte;
         case S is
            when A_IO => null;
            when B_IO => R0 : Byte;
         end case; 
      end record with Unchecked_Union;
      for Shared_IO use record
         R0 at 0 range 0 .. 7;
         R1 at 1 range 0 .. 7;
      end record;

      type Root is abstract tagged private;
      
      type IO_Access is access all Shared_IO;
      
      function Get_IO_Access (R : in out Root) return IO_Access;
   private
      type Root is abstract tagged record
         IO : aliased Shared_IO (B_IO); -- Could be either A_IO/B_IO
      end record;
   end Devices;
      
   package A is
      
      type Device is new Devices.Root with private;
      
      procedure Test (Dev : in out Device);
      
   private
      
      type A_IO_Access is access all Devices.Shared_IO (Devices.A_IO);
   
      type Device is new Devices.Root with record
         IO : A_IO_Access;
      end record;
      
   end A;
   
   package B is
      
      type Device is new A.Device with private;
      
      overriding
      procedure Test (Dev : in out Device);
      
   private
      
      type B_IO_Access is access all Devices.Shared_IO (Devices.B_IO);
   
      type Device is new A.Device with record
         IO : B_IO_Access;
      end record;
      
   end B;
   
   package body Devices is
      function Get_IO_Access (R : in out Root) return IO_Access is
      begin
         return R.IO'Unchecked_Access;
      end Get_IO_Access;
   end Devices;
   
   package body A is
      procedure Test (Dev : in out Device) is
      begin
         --  This assignment would typically be done upon object initialization
         Dev.IO := A_IO_Access (Get_IO_Access (Dev));
         --  Visibility tests
         Dev.IO.R0 := 0; --  Triggers compiler warning (GOOD! Unsure why that wouldn't be a compile time error though)
         Dev.IO.R1 := 1; --  Legal
      end Test;
   end A;
   
   package body B is
      overriding
      procedure Test (Dev : in out Device) is
      begin
         --  This assignment would typically be done upon object initialization
         Dev.IO := B_IO_Access (Get_IO_Access (Dev));
         --  Visibility tests
         Dev.IO.R0 := 0; --  Legal
         Dev.IO.R1 := 1; --  Legal
      end Test;
   end B;

我不太相信这是一种好方法,但这是一种。

使用泛型来限制视图

使用泛型,我们只需访问某些视图即可,而无需涉及繁琐且可能有问题的访问类型(无需担心初始化或意外覆盖它们)。

   type IO_Select is (A_IO,B_IO);
   type Shared_IO (S : IO_Select) is record
      R1 : Byte;
      case S is
         when A_IO => null;
         when B_IO => R0 : Byte;
      end case;
   end record with Unchecked_Union;
   for Shared_IO use record
      R0 at 2 range 0 .. 7;
      R1 at 3 range 0 .. 7;
   end record;

   generic
      S : IO_Select;
   package Common is
      type Common_Device is tagged record
         IO : Shared_IO (S);
      end record;
         
      procedure Test (Dev : in out Common_Device);
   end Common;
   
   package body Common is
      procedure Test (Dev : in out Common_Device) is
      begin
         Dev.IO.R0 := 0; -- Will trigger warning upon generic instantiation with IO_Select (A_IO)
         Dev.IO.R1 := 0; -- Will work fine on either generic instantiation
      end Test;
   end Common;
   
   package A is
      package Common_A is new Common (A_IO);
      
      type Device_A is new Common_A.Common_Device with null record;
      
      overriding procedure Test (Dev : in out Device_A);
   end A;
   
   package body A is
      overriding procedure Test (Dev : in out Device_A) is
      begin
         Dev.IO.R0 := 0; -- Triggers compiler warning
         Dev.IO.R1 := 0; -- Works fine
      end Test;
   end A;
   
   package B is
      package Common_B is new Common (B_IO);
      
      type Device_B is new Common_B.Common_Device with null record;
      
      overriding procedure Test (Dev : in out Device_B);
   end B;
   
   package body B is
      overriding procedure Test (Dev : in out Device_B) is
      begin
         Dev.IO.R0 := 0; -- Works fine
         Dev.IO.R1 := 0; -- Works fine
      end Test;
   end B;