是否可以将变体记录存储到Ada中的泛型类型?

时间:2013-07-29 11:51:15

标签: generics record ada

我有一个从套接字读取数据的通用库。请参阅问题末尾的代码清单。

My_Type是固定大小类型时,此方法正常,但尝试使用此代码读取变体记录会使用消息STORAGE ERROR引发object too large

我知道可以通过套接字发送变体记录,因为我有一个工作示例。我假设问题在于我将记录存储到泛型类型。无论变量记录是否具有默认判别式,都会引发异常。有没有办法在这种情况下存储变体记录?

reader_pkg.ads

with Sockets; use Sockets;
with Sockets.Stream_IO; use Sockets.Stream_IO;

generic
  My_Type: private
package Reader_Pkg is
  task type Receive_Task_Type is
    entry Start(FD: Socket_FD);
  end Receive_Task_Type;
end Reader_Pkg;

reader_pkg.adb

package body Reader_Pkg is
  task body Receive_Task is
    Recv_Socket: Socket_FD;
    Recv_Stream: aliased Socket_Stream_Type;
  begin
    select
      accept Start (FD : Socket_FD) do
        Recv_Socket := FD;
        Initialize (Recv_Stream, Recv_Socket);
        declare
          Message: My_Type := My_Type'Input(Recv_Stream'Access); -- STORAGE_ERROR raised here
        begin
          -- Message gets processed here
        end;
      end Start;
    or
      terminate;
    end select;
  end Receive_Task;
end Reader_Pkg;

3 个答案:

答案 0 :(得分:2)

我知道你说问题发生了,不管该类型是否有默认判别,但我认为这可能与ARM 3.7(28)有关:

  

如果区分类型的判别式具有default_expressions,   然后允许类型的无约束变量和值   可以通过赋予这种判断来改变判别式   变量。如果没有为判别式提供默认值,那么全部   通过显式约束来约束类型的变量   或者按其初始值;这种判别的价值观   初始化后无法更改变量。

如果您声明类似

的类型
type Rec (Len : Natural) is record
   Data : String (1 .. Len);
end record;

然后,一个实例创建后,其固定值为Len。但是,如果你说

type Rec (Len : Natural := 4) is record
   Data : String (1 .. Len);
end record;

然后可以更改实例中Len的值(仅通过赋值给整个对象),这意味着(对于GNAT;其他一些编译器也这样做),编译器必须保留足够的空间最大可能值的堆栈;在这种情况下,这意味着为长度为2 ^ 31的字符串保留足够的空间 - 1.这不会起作用。

如果在编译时启用了其他警告,GNAT会在编译时警告您这个问题(我使用-gnatwa,这是所有常见的警告)。应使用-fstack-check来改进运行时检测。

在上面的案例中,GNAT说

sm.adb:20:09: warning: creation of "Rec" object may raise Storage_Error

避免此问题的一种方法可能是告诉编译器您不会分配给该对象:

Message: constant My_Type := My_Type'Input(Recv_Stream'Access);
         ^^^^^^^^

和另一个(不希望编译器足够聪明以识别这种情况)是限制最大可能的大小:

subtype Length is Natural range 0 .. 1024;
type Rec (Len : Length := 4) is record
   Data : String (1 .. Len);
end record;

答案 1 :(得分:0)

当然可以。实际上存储是容易的部分,加载有点棘手,因为判别式作为类型的一部分;也就是说你不能有无约束的变量。这与S : String;的问题相同,编译器无法使用这样的变量,因为它无法知道要为其分配多少内存。

解决此问题的一种方法是使用访问类型,因为我们可以访问不受约束的类型。以下是这样的解决方案。

Pragma Ada_2012;
Pragma Assertion_Policy( Check );

With
System,
Text_Count,
Unchecked_Deallocation,     
Ada.Strings.Fixed,
Ada.Text_IO.Text_Streams;

Procedure Test is

Type Boolean_Array is array (Positive range <>) of Boolean
  with Pack, Component_Size => 1;    

Type Varient_Record( Length : Positive:= 3 ) is record
    Data : Boolean_Array(1..Length):= (others => True);
end record;

Test : constant Varient_Record:=
          (Data => (True, False, False), others => <>);

Function Associate( Name : String;
            Mode : Ada.Text_IO.File_Mode ) 
    Return Ada.Text_IO.File_Type is
Use Ada.Text_IO;
begin
Return Result : File_Type do
    Open(
      File  => Result,
      Name  => Name,
      Mode  => Mode
    );
end return;
End Associate;


Begin
    Ada.Text_IO.Put_Line("Starting Test:");

    SAVE:
    declare
        Test_File : Ada.Text_IO.File_Type:= 
          Associate( "testing.txt", Ada.Text_IO.Out_File);
        Test_Stream : Ada.Text_IO.Text_Streams.Stream_Access:=
          Ada.Text_IO.Text_Streams.Stream( Test_File );
    begin
        Varient_Record'Write( Test_Stream, Test );
            -- Needs an end-line or something to avoid END_ERROR on read.
        String'Write( Test_Stream, ASCII.CR & ASCII.LF );
        Ada.Text_IO.Close( Test_File );
    end SAVE;

    LOAD:
    declare
        Type Access_Varient is Access Varient_Record;
        Procedure Free is new Unchecked_Deallocation(
            Object => Varient_Record,
            Name   => Access_Varient
            );
        Loaded : Access_Varient:= New Varient_Record'(others=><>);
        Test_File : Ada.Text_IO.File_Type:= 
              Associate( "testing.txt", Ada.Text_IO.In_File);
        Test_Stream : Ada.Text_IO.Text_Streams.Stream_Access:=
          Ada.Text_IO.Text_Streams.Stream( Test_File );
    begin
        Varient_Record'Read( Test_Stream, Loaded.All );

        Ada.Text_IO.Put_Line( "Test and Loading are" &
              (if Test /= Loaded.All then " NOT " else " ") & 
            "the same." );

        Free(Loaded);

    Ada.Text_IO.Close( Test_File );
    End LOAD;


    Ada.Text_IO.Put_Line("Testing complete.");
End Test;

答案 2 :(得分:0)

您不能以这种方式使用您的代码。这里,消息是在开始之后声明的,这是非法的。

但你可以使用类似的东西:

declare
  Recv_Socket : Socket_FD;
  Recv_Stream : aliased Socket_Stream_Type;
begin
  -- maybe init your stream here
  declare
    Message : My_Type := My_Type'Input(Recv_Stream'Access);
  begin
    DoSomethingWithMessage(Message);
  end;
end;

如果My_Type具有可变宽度,则必须知道声明它时使用的实例的大小,并且给出初始值是指定变量大小的一种方法。