如何将对约束字符串的访问传递给 Ada 中的子程序

How to pass access to a constrained string to a subprogram in Ada

好的。我真的很讨厌问这样简单的问题,但我已经读完了三本不同的书,向我解释了访问类型和参数模式,我不明白我做错了什么。

我正在用 Ada 创建一个简单的 shell,因为我对此很感兴趣,而且我认为到目前为止这是一次很好的学习经历。这是我的代码:

with Ada.Text_IO;
with Execute_System;

procedure Main is
   package IO renames Ada.Text_IO;
   Input : aliased String(1 .. 255) := (others=> ' ');
   Last: Integer;

begin
   IO.Put_Line("Welcome to ash! This is an extreme work in progress.");
   Main_Loop:
   loop
      Input := (others=> ' ');
      IO.Put("ash> ");
      IO.Get_Line(Input, Last);
      if Input(Input'First..Last) = "quit" then
       exit Main_Loop;
      else
       Execute_System(Command => Input'Access);
      end if;
   end loop Main_Loop;
end Main;

Execute_System() 所做的是传递给 Spawn,后者又由 GNAT.OS_Lib 库提供。我在编译时遇到的错误是:

main.adb:6:04: warning: aliased object has explicit bounds
main.adb:6:04: warning: declare without bounds (and with explicit initialization)
main.adb:6:04: warning: for use with unconstrained access
main.adb:19:36: object subtype must statically match designated subtype
execute_system.adb:5:60: prefix of "Access" attribute must be aliased
gnatmake: "main.adb" compilation error

我不明白为什么我不能访问这个字符串,只是因为它有明确的界限。我在 new subtype Command_Access is access all String(1..255) 中看到了一个解决方案,但我不明白为什么这是一个解决方案(也请原谅语法错误,我对子类型化还是个新手)。

有人可以阐明我的问题吗?我已经用没有访问参数模式的硬编码值测试了 Execute_System 过程,所以我不认为这是这个问题。

这是由于一条相当晦涩的规则 (RM 3.10.2(27ff))。但原因与实施困难有关。

当变量或参数的类型 access String 没有边界时,必须有一种方法在使用变量或参数时获取边界:

procedure Some_Procedure (A : access String) is 
     First, Last : Integer;
begin
     First := A'First;
     Last := A'Last;
     ...
end Some_Procedure;

如果 A 本质上只是字符串第一个字符的地址,那么将无法计算 A'FirstA'Last.

解决这个问题的一种流行方法是将字符串的边界存储为字符串第一个字符之前的两个整数。然后,当 S'Access 用作 access String; 变量或参数的值时,代码知道字符串的第一个字符将在边界之前,因此它可以检索它们以获得A'FirstA'Last 的值。

这个解决方案的感知问题是它意味着 每个 别名 String 必须存储这些边界。 (我认为只有 aliased 个对象才需要。)如果你说

S : aliased String(1..100);

然后编译器必须生成边界,因为它无法判断在程序中的某个点(甚至可能在不同的包中),代码可能会尝试使用 S'Access 作为access String; 的值。即使从未像那样使用 S'Access ,也必须存储这些边界,因为编译器将无法预测将来哪些代码可能会这样做。这会导致浪费space。这不是一件好事,因为嵌入式系统是 Ada 的主要目标之一。

妥协是规定如果别名 String S 没有边界作为类型的一部分,那么边界将被存储,你可以使用 S'Access 对于 access String。如果别名 String 确实有边界作为子类型的一部分,那么边界将不会被存储,但是你不能将 S'Access 用作 access String (你仍然可以将它用作access String(m..n) 如果边界匹配)。这意味着在这种情况下,边界被存储:

Input : aliased String := (1 .. 255 => ' ');

但在这种情况下他们不是:

Input : aliased String(1 .. 255) := (others=> ' ');

第一种形式是您可以在您的案例中用来解决问题的形式。

如果 Ada 有办法编写第二种类型的声明,但仍然告诉编译器像第一种类型一样对待它,那就太好了——即存储边界并允许 'Access 用作 access String。事实上,我相信那里有一个 Ada Issue(我不想查找)为此提出了一个可能的语法。我记得,有一些关于几种可能的语法的讨论,它们都很丑陋,所以这个问题被搁置了,但 Ada 的未来版本可能会提供一个解决方案。

回答

尝试将 Input'Access 更改为 Input'Unrestricted_Access

属性Unrestricted_Access

属性 Unrestricted_Access 是 GNAT 的一个特性。它可以用于任何类型。

示例 1

注释的代码行不起作用。

type String_Access is access all String;

function Str_10 return String is
   S : String (1 .. 10);
begin
   return S;
end;

Str_1 : aliased String := "0123456789";
Str_2 : aliased String := Str_10;
Str_3 : aliased String (1 .. 10) := (others => '0');
Str_Acc_1 : String_Access := Str_1'Access;
Str_Acc_2 : String_Access := Str_2'Access;
--Str_Acc_3 : String_Access := Str_3'Access;
--Str_Acc_4 : String_Access := Str_3'Unchecked_Access;
Str_Acc_5 : String_Access := Str_3'Unrestricted_Access;

示例 2

工作demo在行动。

With Ada.Text_IO; Use Ada.Text_IO;  
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;

procedure Program is
    type String_Access is access all String;
    Str_2 : aliased String (50 .. 60);
    Str_3 : aliased String (1 .. 10) := (others => '0');
    Str_Acc_5 : String_Access := Str_3'Unrestricted_Access;
begin
    Put (Str_Acc_5'First);
    New_Line;
    Put (Str_Acc_5'Last);
    Str_Acc_5 := Str_2'Unrestricted_Access;
    New_Line;
    Put (Str_Acc_5'First);
    New_Line;
    Put (Str_Acc_5'Last);
end Program;

外部资源

https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gnat_rm/Unrestricted_005fAccess.html

The Unrestricted_Access attribute is similar to Access except that all accessibility and aliased view checks are omitted. This is a user-beware attribute. It is similar to Address, for which it is a desirable replacement where the value desired is an access type. In other words, its effect is identical to first applying the Address attribute and then doing an unchecked conversion to a desired access type. In GNAT, but not necessarily in other implementations, the use of static chains for inner level subprograms means that Unrestricted_Access applied to a subprogram yields a value that can be called as long as the subprogram is in scope (normal Ada 95 accessibility rules restrict this usage).

It is possible to use Unrestricted_Access for any type, but care must be exercised if it is used to create pointers to unconstrained objects. In this case, the resulting pointer has the same scope as the context of the attribute, and may not be returned to some enclosing scope. For instance, a function cannot use Unrestricted_Access to create a unconstrained pointer and then return that value to the caller.

评论

@ajb 在评论中指出 bouandery Str_Acc_5'First = Str_3'FirstStr_Acc_5'Last = Str_3'Last 可能是也可能不是真的。目前我还找不到官方文档。另一方面 boundary is matching on ideone.com.