1

In Ada 2012, I want to have a linked list inside a declared array, rather than allocated. I want to have the linking pointers set by a procedure.

I have simplified my program to the following that demonstrates the principle I want to use but I cannot get this to compile in Gnat 4.9.2 (Debian Jessie) running on Raspi...

procedure Arr is
   type Cell;
   type Cell_Ptr is access all Cell;

   type Cell is
      record
         Number : Integer := 0;
         Next : Cell_Ptr := null;
      end record;

   type Chain is array (1 .. 100) of aliased Cell;

   procedure Make_Links (Ch : in out Chain);

   procedure Make_Links (Ch : in out Chain) is
   begin
      for I in Ch'First .. Ch'Last - 1 loop
         Ch (I).Next := Ch (I + 1)'Access;                 -- ERROR HERE
      end loop;
   end Make_Links;

   My_Chain : Chain;
begin
   Make_Links (My_Chain);
end Arr;

I get this compiler error: "non-local pointer cannot point to local object" at the line indicated above.

I know I'm trying to do something a bit odd but I plan on having a few other functions that perform the linking in different ways (backwards, or randomly etc) based on which procedure I pass this array of cells to.

How do I fix this code so that it compiles? Can't quite get my head around this one (I'm still a novice but enjoying the learning process).

  • I'm not sure this will work, but try changing the parameter to `Ch : access Chain` and the call to `Make_Links (My_Chain'access)`. – ajb Aug 05 '15 at 05:20
  • ... and you'd have to declare `My_Chain` as `aliased`, of course. – ajb Aug 05 '15 at 05:38

3 Answers3

3

As you are not actually allocating & freeing memory, in dont see the need for pointers. I would achieve the same functionality by doing something like this:

procedure Arr is

   type Cell_Index is new Integer range 0 .. 100;
   subtype Valid_Cell_Index is Cell_Index range 1 .. Cell_Index'Last;

   type Cell is
      record
         Number : Integer := 0;
         Next   : Cell_Index := 0;
      end record;

   type Chain is array (Valid_Cell_Index) of Cell;

   procedure Make_Links (Ch : in out Chain);

   procedure Make_Links (Ch : in out Chain) is
   begin
      for I in Valid_Cell_Index'First .. Valid_Cell_Index'Last - 1 loop
         Ch (I).Next := I+1;
      end loop;
   end Make_Links;

   My_Chain : Chain;
begin
   Make_Links (My_Chain);

end Arr;

This way you are still using Next as an index into the same array, and can pre-load your array with whatever linking pattern you want.

NWS
  • 3,080
  • 1
  • 19
  • 34
  • Yeah, I'd normally agree but I had to simplify my real source code in order to post it here. I have a good reason for attempting this, it's just the syntax I'm fighting with. The real code (currently a "proof of concept" model) has to do this in a 2 dimensional array with 8 separate pointers. Eventually the code will be expanded to 3 dimensions and 26 pointers per record!!! –  Aug 04 '15 at 17:29
  • So why not enumerate your pointers, and have arrays of indexes ? You can also make all the indexes subtypes of a parent type which should also prevent invalid indexing. – NWS Aug 04 '15 at 18:21
  • I don't know why not, really. I'll shall persevere. –  Aug 04 '15 at 18:40
0

Instead of using ’Access, use ’Unrestricted_Access. This is one of GNAT’s “implementation-defined” attributes:

The Unrestricted Access attribute is similar to Access except that all accessibility and aliased view checks are omitted. This is a user-beware attribute.

Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • I'd rather not go outside of "best practice" really while I'm learning Ada. There must be a clean, safe way to do this. I want to use Ada *because* it has checks and balances in it to help me out, and I don't want to circumvent them :) –  Aug 04 '15 at 16:33
  • If you're going to go this route, why not use `'Unchecked_Access`, which has an Ada definition and is portable, rather than the GNAT-specific attribute? `'Unchecked_Access` won't omit aliased view checks, but that's not a problem here. – ajb Aug 05 '15 at 05:22
  • @Wossname There _might_ be a clean, safe way to do it. Unfortunately, the goal of Ada, which was to provide a safe way to do these access checks without doing anything that would require inefficient implementation when it wasn't needed, has proven too tough for a group of mortals. The result is RM 3.10.2, known as the "Heart of Darkness" to Ada experts. I invite you to try to read it, but not without a bottle of Advil handy. And even so, I don't think every use case is handled adequately. – ajb Aug 05 '15 at 05:28
  • @ajb is right about `’Unchecked_Access`, which would have been a better answer. – Simon Wright Aug 05 '15 at 07:20
0

I figured it out in the end. The following code is a modified version of the one in the OP. It does what I originally wanted without doing anything unpleasant...

with Ada.Integer_Text_IO, Ada.Text_IO;
use Ada.Integer_Text_IO, Ada.Text_IO;

procedure Arr is
   type Cell;
   type Cell_Ptr is access all Cell;

   type Cell is
      record
         Number : Integer := 0;
         Next : Cell_Ptr := null;
      end record;

   type Chain is array (1 .. 100) of aliased Cell;
   type Chain_Ptr is access all Chain;

   procedure Make_Links (CP : in out Chain_Ptr);

   procedure Make_Links (CP : in out Chain_Ptr) is
   begin
      for I in CP'First .. CP'Last - 1 loop
         CP.all (I).Next := CP.all (I + 1)'Access;
      end loop;
   end Make_Links;

   My_Chain : aliased Chain;
   My_CP : Chain_Ptr := null;
   My_C : Cell_Ptr := null;
begin
   My_CP := My_Chain'Access;
   Make_Links (My_CP);

   --  verify that the code works by writing values into the array
   for I in My_Chain'Range loop
      My_Chain (I).Number := 1000 * I;
   end loop;

   --  and read them back out using the pointer links
   My_C := My_Chain (My_Chain'First)'Access;
   while My_C /= null loop
      Put (My_C.Number);
      Put_Line ("");
      My_C := My_C.Next;
   end loop;

end Arr;

Instead of passing the array directly, I passed a pointer to the array instead, which Gnat seems happy with. I think what I was trying to do before was being scuppered by the "pass by copy" rules for procedure parameters.

  • I don't think the "pass by copy" rules have anything to do with it. It's all about the accessibility rules. When you have a `Chain` as a parameter, you _could_ pass a `Chain` that's declared in a deeply nested inner procedure, and that's a problem because `'Access` on the elements could store pointers to objects that could live after the objects disappear. You can't do that with `Chain_Ptr` because a `Chain_Ptr` isn't allowed to point to anything that's more deeply nested than the `Chain_Ptr` declaration. – ajb Aug 09 '15 at 00:00