Pointers are extremely useful for certain types of operations: they provide a way to refer indirectly to other variables or names (they act as an alias), or can be used for establishing dynamic data structures.
A pointer may be declared by adding the pointer
attribute to
the relevant data type, e.g.,
integer, pointer :: p => null()
In this case we declare a pointer to integer p
, which is initialised to
the special value null()
. The pointer is said to be unassociated.
This is a different state to undefined (ie., without initialisation).
Note that pointer assignment uses =>
and not =
. A common cause of
errors is to forget the >
if pointer assignment is wanted.
It is important to be able to check that a given pointer is not null()
.
This is done with the associated()
intrinsic; schematically,
integer, pointer :: p => null()
...
if (associated(p)) ... do something
Otherwise, pointers may be used in expressions and assignments in the usual way.
If one wishes to have a pointer to an array, the rank of the pointer should be the same as the target:
real, dimension(:), pointer :: p1
real, dimension(:,:), pointer :: p2
and so on.
A pointer may be associated with another variable of the appropriate type (which is not itself a pointer) by using the target attribute:
integer, target :: datum
integer, pointer :: p
p => datum
The pointer is now said to be associated with the target. We can now
perform operations on datum
vicariously through p
. E.g., a
standard assignment would be
integer, target :: datum = 1
integer, pointer :: p
p => datum ! pointer assignment
p = 2 ! normal assignment
leaves us with datum = 2
. There is no dereferencing in the C fashion;
the data is moved as the result of the normal assignment.
The target attribute is there to provide information to the compiler
about which variables may be, and which variables may not be,
associated with a pointer. This somewhat in the same spirit as the
C restrict
qualifier.
Note that there is an optional target argument to the associated()
intrinsic, which allows the programmer to inquire whether a pointer
is associated with a specific target, e.g.,
associated(p, target = datum) ! .true. if p => datum
Try to compile the accompanying example1.f90
, which is an erroneous
version of the code above. See what compiler message you get (if any).
Fix the problem.
Check you can use the associated()
function to print out the status
of p
before and after the pointer assignment.
One common use of pointers is to provide a temporary alias to another
variable (where no copying takes place). As a convenience, one can
use the associate
construct, e.g.:
real :: improbably_or_tediously_long_variable_name
...
associate(p => improbably_or_tediously_long_variable_name)
! ... lots of operations involving p ...
end associate
Note that there is no requirement here to have the target
attribute
in the original declaration (and there's no explicit declaration of p
).
Any update to p
in the associate block will be reflected in the
target on exit.
Compile, and check the output of the accompanying example2.f90
.
One common use of pointers is for linked data structures. For example, an entry in a linked list might be represented by the type
type :: my_node
integer :: datum
type (my_node), pointer :: next
end type my_node
This sort of dynamic data structure requires that we establish or destroy storage as entries are added to the list, or removed from the list.
subroutine my_list_add_node(head, datum)
! Insert new datum at head of list
type (my_node), pointer, intent(inout) :: head
integer, intent(in) :: datum
type (my_node), pointer :: pnode
allocate(pnode) ! assume no error
pnode%datum = datum
pnode%next => head
head => pnode
end subroutine my_list_add_node
The question may now arise: should you use pointers or allocatable arrays?
If you just want to establish storage for arrays, the answer is almost
certainly that you should use allocatable
. An allocatable array will
almost certainly be held contiguously in memory, whereas pointers are
a more general data structure which may have to accommodate a stride.
If an allocatable array is not appropriate, then a pointer may be required.
If you just require a temporary alias, the associate
construct is
recommended.
If one needs to increase (or decrease) the size of an existing
allocatable array, the move_alloc()
intrinsic is useful. E.g.,
if we have an integer rank one array
integer, dimension(:), allocatable :: iorig
and establish storage of a given size, and some relevant initialisations, we may then wish to increase the size of it.
integer :: nold
integer, dimension(:), allocatable :: itmp
nold = size(iorig)
allocate(itmp(2*nold)) ! double size; assume no error
itmp(1:nold) = iorig(1:nold) ! copy existing contents explicitly
call move_alloc(itmp, iorig)
! ... itmp deallocated, iorig now twice as big
This minimises the number of copies involved in re-assigning the original storage.
A small trick is required to arrange an array of pointers. Recall that
real, dimension(:), pointer :: a
is a pointer to a rank one array. If one wanted an array of such objects, it can be achieved by wrapping it in a type:
type :: pointer_rr1
real, dimension(:), pointer :: p => null()
end type pointer_rr1
type (pointer_rr1), dimension(10) :: a
a(1)%p => null()
So a
is a rank one array of the new type, the target of each of which
should be a rank one array section of type real
.
A thought exercise. How many copies would be required if move_alloc()
was not available when enlarging the size of an existing allocatable
array?