简体   繁体   中英

What is the 'Address of an Ada subprogram passed as an access parameter?

Compiling the following program without optimization, and then running it, I see Program_Error ,

raised PROGRAM_ERROR : addreq.adb:16 explicit raise

or, updating in view of Simon Wright's answer,

raised PROGRAM_ERROR : using address

This happens when using GNAT GPL 2014 on either Mac OS X or on GNU/Linux x84_64, on Linux, strangely, only for my program. Other versions of GNAT produce code that doesn't raise, older compilers do not accept (access-to-function parameters being more recent, I'm not surprised). Since my program needs to identify addresses of subprograms, I was hoping for a definitive statement in the RM; 13.3 (11) has ramifications that grow waivers, IINM . So, referring to the program below, would

Yes_no.all'Address = No'Adress

be a true statement if interpreted by the LRM? Legit? Is Yes_No.all actually a proper way to refer to the function No (if taking the 'Address )? As there is indirection through different pointer types, one having deeper accessibility, does this change the picture? I was thinking that Yes_No.all should yield the same 'Address as No , but apparently not, with some compilers.

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;

One more update: if I make Addreq a package and have another subprogram call Taking , thus

with Addreq;  --  now a package
procedure Driver is
   use Addreq;
begin
   Taking (No'Access);
end Driver;

then no exception is raised.

I think it must depend on your OS and compiler. Using FSF GCC 5.1.0 on Mac OS X, your code doesn't raise the exception.

That said, I think it'd be more natural to avoid .all'Address (I was told by one of the Ada 95 Distinguished Reviewers that he'd got into the habit of saying .all'Access when what was really needed was an appropriate type conversion). This extension of your code doesn't raise the exception for either case.

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;

(later)
I rewrote this to not use exceptions ...

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;

With GNAT GPL 2014 on Mac OS X, this gives

$ ./addreq 
using address failed
using access was OK

If Yes_No.all'Address is not equal to No'Address , then most likely Yes_No.all'Address is the address of some kind of wrapper code.

No is a function nested inside a procedure. If you say No'access , the compiler generally cannot simply create a one-word pointer whose value is the address of No . The reason is that when the code makes an indirect call through the access value, the code has to do something special so that No can access local variables belonging to addreq , which will be somewhere on the stack. For example, one way to provide this access is to pass a static link as a parameter to No ; this is an extra pointer that points to addreq 's stack frame, which will contain its local variables (or something along those lines). Thus, when an indirect call is made through the access, the caller has to know what the static link is. One solution is to make nested access-to-function types dope vectors, that contain the function address and the static link. Another is to generate wrapper code. The wrapper code is responsible for calling the called subprogram with the static link parameter, and the access value is then simply a one-word pointer, which is the address of the wrapper code. I believe GNAT takes this approach. The advantage is that it makes it possible to pass My_Function'access as a parameter to a C function, for use as a callback. When the C code calls through the function pointer, it calls the wrapper function which then calls the nested function with the correct static link. There is a significant amount of public Ada code that depends on this mechanism. (GtkAda relies heavily on it.)

However, if the access value points to a wrapper, instead of the actual function, then The_Access.all'Address won't return what you think it should. When the code executes The_Access.all'Address , if The_Access is a single word with an address in it, that's all the attribute can return--the address in the pointer.

I don't know whether the original code is part of a larger example or just a test to see what the compiler does. 我不知道原始代码是较大示例的一部分,还是仅仅是测试以查看编译器的功能。 But comparing 'Address values to see if a subprogram-access parameter refers to a specific subprogram strikes me a poor design, and comparing 'Access is no better. I would avoid doing that even in C. There's likely to be a more object-oriented solution to the problem (note that you can use tagged types to cause indirect subprogram calls to take place, because tagged type operations are dispatching). Eg

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;

Might not be the best design in all cases, but something like this should be considered if there seems to be a need to check a subprogram address to a particular subprogram. For one thing, this is more flexible; a programmer can define another derived type where Apply always return False but does something else, such as writing the argument to a log file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM