什么是作为访问参数传递的Ada子程序的地址?

时间:2015-06-04 16:34:46

标签: ada

在没有优化的情况下编译以下程序,然后运行它,我看到Program_Error

raised PROGRAM_ERROR : addreq.adb:16 explicit raise
鉴于Simon Wright的回答

更新

raised PROGRAM_ERROR : using address

在Linux上使用GNAT GPL 2014或在Linux上使用GNU / Linux x84_64时会发生这种情况,奇怪的是,仅适用于我的程序。其他版本的GNAT产生的代码不会引发,旧的编译器不接受(访问功能参数更新,我并不感到惊讶)。由于我的程序需要识别子程序的地址,我希望在RM中有明确的陈述; 13.3 (11) has ramifications that grow waivers, IINM。所以,参考下面的程序,

Yes_no.all'Address = No'Adress
如果由LRM解释,

是真实的陈述吗?合法的? Yes_No.all实际上是引用函数No的正确方法(如果使用'Address)?由于存在通过不同指针类型的间接,具有更深入可访问性的指针类型,这是否会改变图片?我认为Yes_No.all应该与'Address产生相同的No,但显然不会产生一些编译器。

with System;

procedure Addreq is

   function No (Ignored : Integer) return Boolean is
   begin
      return False;
   end No;

   procedure Taking
     (Yes_No : access function (N : Integer) return Boolean)
   is
      use type System.Address;
   begin
      if Yes_No.all'Address /= No'Address then
         raise Program_Error;
      end if;
   end Taking;

begin
   Taking (No'Access);
end Addreq;

还有一次更新:如果我将Addreq打包并让另一个子程序调用{​​{1}},那么

Taking

然后没有异常被提出。

2 个答案:

答案 0 :(得分:3)

我认为它必须依赖于您的操作系统和编译器。在Mac OS X上使用FSF GCC 5.1.0,您的代码不会引发异常。

那就是说,我认为避免.all’Address更为自然(Ada 95杰出评论家之一告诉我,他习惯于说.all’Access什么时候真的需要是一个合适的类型转换)。这种代码扩展不会引发任何一种情况的异常。

with Ada.Text_IO; use Ada.Text_IO;
with System;

procedure Addreq is

   function No (Ignored : Integer) return Boolean is
   begin
      return False;
   end No;

   procedure Taking
     (Yes_No : access function (N : Integer) return Boolean)
   is
      use type System.Address;
   begin
      if Yes_No.all'Address /= No'Address then
         raise Program_Error with "using address";
      end if;
      Put_Line ("using address was OK");
      if Yes_No /= No'Access then
         raise Program_Error with "using access";
      end if;
      Put_Line ("using access was OK");
   end Taking;

begin
   Taking (No'Access);
end Addreq;

(稍后)
我重写了这一点,不使用例外......

with Ada.Text_IO; use Ada.Text_IO;
with System;

procedure Addreq is

   function No (Ignored : Integer) return Boolean is
   begin
      return False;
   end No;

   procedure Taking
     (Yes_No : access function (N : Integer) return Boolean)
   is
      use type System.Address;
   begin
      Put_Line
        ((if Yes_No.all'Address /= No'Address
          then "using address failed"
          else "using address was OK"));
      Put_Line
        ((if Yes_No /= No'Access
          then "using access failed"
          else "using access was OK"));
   end Taking;

begin
   Taking (No'Access);
end Addreq;

在Mac OS X上使用GNAT GPL 2014,这就是

$ ./addreq 
using address failed
using access was OK

答案 1 :(得分:1)

如果Yes_No.all'Address不等于No'Address,则很可能Yes_No.all'Address是某种包装器代码的地址。

No是嵌套在过程中的函数。如果你说No'access,编译器通常不能简单地创建一个单字指针,其值是No的地址。原因是当代码通过访问值进行间接调用时,代码必须执行一些特殊操作,以便No可以访问属于addreq的局部变量,这些变量将位于堆栈的某个位置。例如,提供此访问权限的一种方法是将静态链接作为参数传递给No;这是一个额外的指针,指向addreq的堆栈帧,它将包含其局部变量(或沿着这些行的某些东西)。因此,当通过访问进行间接调用时,调用者必须知道静态链接是什么。一种解决方案是制作嵌套的访问功能类型的dope向量,其中包含函数地址和静态链接。另一个是生成包装器代码。包装器代码负责使用静态链接参数调用被调用的子程序,然后访问值只是一个单字指针,它是包装器代码的地址。我相信GNAT采用这种方法。优点是它可以将My_Function'access作为参数传递给C函数,以用作回调。当C代码通过函数指针调用时,它调用包装函数,然后使用正确的静态链接调用嵌套函数。有大量的公共Ada代码依赖于这种机制。 (GtkAda严重依赖它。)

但是,如果访问值指向包装器而不是实际功能,则The_Access.all'Address不会返回您认为应该的内容。当代码执行The_Access.all'Address时,如果The_Access是包含地址的单个单词,那么所有属性都可以返回 - 指针中的地址。

更多:我不知道原始代码是一个更大的例子的一部分,还是只是一个测试,看看编译器的作用。但是比较'Address值以查看子程序访问参数是否涉及特定子程序会让我觉得设计不佳,并且比较'Access并不是更好。即使在C中,我也会避免这样做。这可能是一个更面向对象的问题解决方案(请注意,您可以使用标记类型来进行间接子程序调用,因为标记类型操作正在调度) 。 E.g。

type Boolean_Function_Object is abstract tagged null record;
function Apply (Obj : Boolean_Function_Object; N : Integer) return boolean;
function Is_Constant_False (Obj : Boolean_Function_Object) return boolean;

type No_Function is new Boolean_Function_Object with null record;
overriding
function Apply (Obj : No_Function; N : Integer) return boolean is (False);
overriding
function Is_Constant_False (Obj : No_Function) return boolean is (True);

procedure Taking (Func : Boolean_Function_Object) is
begin
    if not Func.Is_Constant_False then
        raise Program_Error;
    end if;
end Taking;

在所有情况下可能不是最好的设计,但是如果需要检查特定子程序的子程序地址,则应考虑这样的事情。一方面,这更灵活;程序员可以定义另一个派生类型,其中Apply总是返回False但执行其他操作,例如将参数写入日志文件。