记录布局的静态比较

Static Comparison of Record Layouts

我有两条记录,其中的某些字段需要位于每条记录中的相同位置。尽管这在代码中已被大量注释,但在 10 年的时间里,程序员可能会更改其中一条记录而不更改另一条记录,我想创建一个静态检查以确保没有发生这种情况。

我可以按如下方式在过程或函数中创建“活动”检查:


procedure Main is

    type SimpleRecord1 is record
        FirstItem  : Integer;
        SecondItem : Boolean;
        ThirdItem  : Integer;
        DoNotCare  : Float;
    end record;

    type SimpleRecord2 is record
        Foo        : Integer;
        Bar        : Boolean;
        Baz        : Integer;
        Rfc3092    : Boolean;
    end record;

    MyRecord1 : SimpleRecord1;
    MyRecord2 : SimpleRecord2;
begin
    Pragma Assert ((MyRecord1.FirstItem'Position = MyRecord2.Foo'Position) and
                   (MyRecord1.SecondItem'Position = MyRecord2.Bar'Position) and
                   (MyRecord1.ThirdItem'Position = MyRecord2.Baz'Position));

    Put_Line ("The assert didn't fire");
          
end Main;

我担心前三个变量在两个记录中具有相同的偏移量。在实际代码中,每条记录中还有许多其他变量,这些变量在记录之间是不同的。

但是,我真的希望这是一个检查,不是针对记录实例(MyRecord1、MyRecord2),而是针对记录本身(SimpleRecord1、SimpleRecord2)。然后它可以放在定义记录的.ads 文件中。

SimpleRecord1.FirstItem'位置

是非法的。有没有一种方法可以创建一个检查而不必创建实例并将代码放入函数或过程中?

为了使最后两条评论(Jere 和 Jim Rogers)更具体,实际上 Ada 方法是定义列表元素的类型,以便任何类型的元素都可以放在同一个列表中,并可以访问通过相同类型的指针,没有任何不可检查的转换。在 OP 的情况下,IMO 最合适的方法是使所有列表元素成为从同一抽象父项 class 派生的标记记录,其中父项包含下一个、上一个和优先级组件。例如像这样:

type List_Element;
type List_Ptr is access List_Element'Class;
type List_Element is abstract tagged record
   Next, Prev : List_Ptr;
   Priority   : Boolean;
end record;

type Simple_Record_1 is new List_Element with record
   DoNotCare : Float;
end record;

type Simple_Record_2 is new List_Element with record
   Rfc3092 : Boolean;
end record;

处理链接列表的 SW 处理指向 List_Element'Class 对象的 List_Ptr 值,但只有公共组件 Next、Prev 和 Priority 可见。当需要根据列表元素的实际类型执行某些处理时,您可以使用动态调度调用,或者使用成员测试然后进行类型转换,从 List_Ptr 到例如底层 Simple_Record_1。

我会这样做 - 特别是如果您要使用地址覆盖 and/or Unchecked_Conversion

-----------------------------------------------------------------------------

   type Header_Record is
      record
         First_Item  : Integer;
         Second_Item : Boolean;
         Third_item  : Integer;
      end record
     with Convention => C;
       
   for Header_Record use
      record
         First_Item  at 0 range  0 .. 31;
         Second_Item at 0 range 32 .. 47;
         Third_Item  at 0 range 48 .. 79;
      end record;
   
-----------------------------------------------------------------------------

   type Item_Record_1 is
      record
         Header    : Header_Record;
         DoNotCare : Float;
      end record
     with Convention => C;
   
   for Item_Record_1 use
      record
         Header    at 0 range  0 .. 79;
         DoNotCare at 0 range 80 .. 111;
      end record;

--------------------------------------------------------------------------------

   type Item_Record2 is
      record
         Header : Header_Record;
         Rfc3092 : Boolean;
      end record
     with Convention => C;
   
   for Item_Record2 use
      record
         Header  at 0 range  0 .. 79;
         Rfc3092 at 0 range 80 .. 95;
      end record;
   
-----------------------------------------------------------------------------

在这里,我们指定了确切的位布局。您需要使用一级间接寻址,即

,这有点痛苦
Item_Record1.Header.First_Item

但是,这应该有效。另外,永远记得使用

Convention => C

由于 Ada 记录布局和 C 结构布局可能有很大差异 - 考虑到 Ada 丰富的语义,这是可以预料的。

编辑:针对可移植性问题。虽然,OP 没有指定它 必须 是可移植的,但它仍然没问题 ...

   with Interfaces.C;

   subtype int is Interfaces.C.int;

   type Header_Record is
      record
         First_Item  : Interfaces.C.int;
         Second_Item : Interfaces.C.int; --bool?
         Third_item  : Interfaces.C.int;
      end record
     with Convention => C;
       
   -- Exact values very likely to be different, for demo only
   for Header_Record use
      record
         First_Item  at 0 range  0 .. int'Size;
         Second_Item at 0 range  int'Size + 1 .. int'Size * 2;
         Third_Item  at 0 range  int'Size * 2 + 1 .. int'Size * 3;
      end record;