Menu Close

Articles

Level: Pointers & References

Pointers are references to a memory locations. Like any other variable, you must declare a pointer before using it. Pointers are useful to improve performance or to create input/output parameters.

Implementation

Pointers are available only in Level-1 compilers. Other compilers do not use pointers. Instead we rename pointers to references. A similar paradigm shift is used in Java, Python, Go and other high level languages. We are not different and we follow the pack so you can learn the natural order of things in programming.

Pointer notation

The general form of a pointer declaration is using “at” symbol “@”. We have chosen this symbol to suggest the memory address location. Using pointers with @type we can define “pointer” variables. We can use unary operator ? to extract address of a variable:

let
  n: Integer;
  p: @Integer; !define a pointer
do
  p := ?n; !transfer address of "n" to "p"
end do;

Primary usage

Assign operator “:=” works “by value“. It is making a full copy of the original value. For composite types: {vector, matrix, record, tuple, string} the assign operator can create large “data movement” that can be avoided by using pointers.

When we use pointers to {@vector, @matrix, @record, @tuple, @string} The memory location is shared using “?=” operator” that is address of variable. This will transfer address into a pointer and this is much faster.

Reference counting

When we initialize a reference to a new address location we create an “owner” variable. This can be created using “=” operator with constructor or expression.

We can share owner with one or more pointers. Every time when a share event take place using “=?” the “owner” reference count is incremented with one. A pointer can re-share the reference and counter of owner is incremented again.

When a pointer go out of scope the “owner” reference count is decremented with one. The “owner” can’t go out of scope until the reference count is zero. This verification is done during compilation time.

Reference count is a compiler time property of “owner” variable. It is incremented and decremented every time an assignment is performed between two variables.

Mutability

If a variable content is modified sometimes the reference is changed and sometimes remain the same. For example when we resize a vector or modify a string there is not enough memory available for the new value at the old location. Therefore we allocate new memory. The older value is now orphan and must be removed from memory. This is a run-time operation that may be expensive.

In-place operators or modifiers: “+=, -=, /=, *=, %=, ^=” do not change the reference. These operators are referring to numbers. Assign operator “:=” and “?=” do. These operators “rebind” the variable to a new location. After this all the pointers referring same “owner” location must be updated. For this we must keep record of connected pointers to the same “owner”.

Address of a pointer can’t be modified using assign or modifier operators:

“`

?variable := value; ! will fail

“`

Sage programming

Level-RC do not have a garbage collector. Both operations must be done immediate. First we update all the pointers and second we must free the memory. Memory cleanup can be done when a wrapping section go out of scope.

Programming example

A good programmer must be aware of memory clearing to organize code using local scope that clear the memory early instead of waiting until the method or the program is finished.

procedure stress() is
  b: Matrix of Integer;
begin
  ... 
  let --> create a local scope
    a: Matrix of Integer; --> this matrix has no members yet
  do
    for i in (1..1000) loop
       a.extend(1000); --> add a new row (rebinding event)
       for j in (1..1000) loop
          a[i-1,j-1]:j; --> set value for new member
       end loop;
    end loop; 
    --> we have memory fragments
    print(a[999,999]); -- last element
    b:=a;
  end do; --> clear memory
  -- no more waste here
  print(b);
end procedure;

Relation Operators

Symbol “==” and “<>” work for pointers. This is a requirement for making a readable program and avoid any confusion about fundamental role of relation operators. If we wish to know if two pointers have same location we use:

“`

?pointer1 == ?pointer2

?variable1 <> ?variable2

“`

Comparing pointers

To check if two pointer values are equal we use “==” and “<>”. This has significance “equal” and “not equal”. If two pointers point to the same location and have the same type then also have the same value so they are equal. If location is divergent the underlying content become relevant and a deep comparison is performed to check if the variables are equal or collections have same exact members.

Example of relation

let
   p:Integer;
   c:@Integer;   
do
  c = ?p;
  if c == p then
     print("equal");
  else
     print("not equal");
  end if;   
done;

=> equal

Pointer Use Cases

There are a four important operations which we will do with the help of pointers.

  • We can share the address of a variable with a pointer using the operators “=” and “?var”;
  • We can initialize a pointer with a literal. In this case the new pointer is the “owner”.
  • We can use pointers as input/output parameters in procedures and functions.
  • We can create records that have references to other records or composite types.
  • We can create collections of references to records.

Example:

-- this is simple example of pointer usage
procedure test is
  v_int=100:Integer;  !variable is initialized
  p_int:@Integer;     !not initialized (Void)
  o_ind=100:@Integer; !initialized with a literal (new)
begin
  p_int ?= v_int; ! equivalent to p_int = ?v_int;
  print(“the value of p_int is ” & p_int); --> 100
  p_int= p_int + 10;
  print(“the value of p_int is ” & p_int); --> 110
  print(“the value of v_int is ” & v_int); --> 110
  if o_int==p_int then
     print("yes o_int is equal to p_int");
  end if;
end procedure;

=> “yes o_int is equal to p_int”

Ada equivalent

If we translate this example is Ada language we can see differences in the notation of pointers. We use @ instead of access and ? instead of n’access notation. Also we end section with section type not with the section name.

with Ada.Text_IO; use Ada.Text_IO; 
procedure test is 
  n : Integer; 
  p : access Integer; -- define a pointer 
begin 
  p := n'access; -- transfer address of "n" to "p" (p is not initialized) 
  p.all := 10;   -- modify the "n" variable from 0 to 10 
  Put_Line (n'Image); -- n has value 10 
end test;

Pointers as Parameters

Parameters of a section can be forced to pass by reference using explicit @type notation. This can be useful to create input/output parameters and support side-effects. The composite types are pass by reference all the time but the compiler verify if we modify the arguments and generate an error unless we use “@” notation to turn off this verification.

Example:

procedure example is
  -- the variable region is implicit
  v_result=10:Integer; --> define a global variable
  p_test:@Integer; --> define a pointer to integer

  -- local procedure
  procedure add_numbers(p1, p2: Integer, p_result: @Integer)
  begin
    p_result = p1+p2; !modify the parameter value
  end procedure;
begin
  add_numbers(10, 20, ?v_result);
  print(v_result); --> New value is 40
  print(p_test); --> Will generate error: p_test is not initialized
end procedure;

Void pointers

In most of the operating systems, programs are not permitted to access memory at address “0 = Void” because that memory is reserved by the operating system. In C and C++ the memory address “0” has special significance; it signals that a pointer is not assigned to an accessible memory location. By convention, if a pointer has value zero (0) then pointer is the “Null Pointer_”. These pointers should not be used until they point to a valid memory address that contain an object.

For Level we can have an uninitialized pointer but we can’t use this pointer until is initialized. We can use “is” operator to check if a pointer “is Void” or “is not Void”. This operator is a polymorphic operator and can be used with a type: (x is Integer) or (r is Record) for example. It check if a variable is of particular type.

Example of void pointer:

---------------------------------------------------------
! this is simple example of late vector initialization 
! using the constructor vector()
---------------------------------------------------------
procedure pointer_example(p_dim:Integer)
  v_int: Vector of Integer;
begin
  if v_int is Void then
     v_int:=Vector(p_dim); !dynamic initialization
  end if;
  print v_int;  
end procedure;

=> [0,1,2,3,4,5,6,7,8,9]

Zero Values versus Void

Level define a Zero value for every data types. This is different from Void. A pointer can be initialized with zero values and later can be changed. To verify if a reference is empty we use empty() built-in or “==” operator. This expression will fail for Void pointers at compile time. A Void pointer cannot be verified if is Empty and can’t be used in expressions except for initialization.

Default Zero Values (empty literals):

  • Empty number is: 0
  • Empty string is: “”
  • Empty tuple is: ()
  • Empty record is: ()
  • Empty vector is: []
  • Empty matrix is: []

Introspection

We can use “is Void” operator to check “status” of variables and pointers:

let
   p,c:  @Integer; !Void pointers
   v:  Integer;  !Initialized variable
do
   -- check the status
   print(p is Void)    -- true 
   print(c is Void)    -- true 
   print(v is Void)    -- false

   v := 1; Modify variable v==0 to v==1
   p := 1; Initialize pointer p become owner
   c ?= v; Initialize c that become share
   -- check the status again
   print(p is Void); -- false
   print(c is Void); -- false
   print(v is Void); -- false
end do;

Recursive records

A structure that is recursive can have one member that represents the parent. The first parent do not have also a parent. So the parent can be a void pointer. When we assign a parent to a record this can be shared pointer or an original.

When the record go out of scope all the originals are free. The shared pointers remain in memory until the original is free. The pointer actually can be removed but the location remain loaded as long as reference count > 0.

Read next: Functional Programming