Fortran 90 POINTERs

Assignment :

Read 18.4 and 18.5 and the Web Notes on Gauss Elimination

New Fortran:

POINTER and TARGET attributes, NULLIFY statement, => operator, ASSOCIATED intrinsic function.

Pointers have always been a key feature in the C programming language. One reason is the way C passes arguments to functions. As you may recall Fortran passes a function the address of an argument, so that both function and calling routine agree on the location of the corresponding variable in the argument list. C simply passes the value of the argument to the function. If variable "ivar" is an argument to a C function, the function itself has no idea where the original variable is located, and cannot change its value. The C function can only use its value as passed through a separate memory location. As you may imagine this also could play havoc with use of arrays as arguments to C functions. The answer in C is the pointer variable, which can be set to contain the actual memory address of a normal C variable. The C declaration:

int *pvar

Sets the variable "pvar" as a pointer to integer type variables. It's the "*" before "pvar", that says we are dealing with a pointer variable. If "ivar" is an integer variable then the statement

pvar = &ivar

results in "pvar" containing an integer giving the address in memory of "ivar". I can then pass "pvar" as an argument to the function, giving it knowledge of the actual location of "ivar" (just as a Fortran function would have), and the ability to use and change the contents of that memory location (ivar), and subsequent locations if "ivar" is an array..

The pointer variable provides more than expanded power in C functions. It gives detailed control over complex data structures. At one time or another most heavy Fortran users have needed this type of capability to efficiently manipulate complex data, and equivalents to the C pointer have been provided on most machines as extensions to Fortran 77. When the Fortran 90 standard was being established, there was a request for this feature. What we got was something a little different, in many respects more powerful, and in some respects intentionally crippled.

One underlying theme of new Fortran 90 constructs has been isolation of users from the details of memory allocation. If you will be running on parallel computers, this is not a bad thing. As a result, the Fortran 90 POINTER does point to variables of your choice, but it won't directly tell you the memory address of that variable. On the surface it behaves like an executable EQUIVALENCE statement, but actually does quite a bit more.

I have mixed emotions on this restrictive approach to memory use. I will gratefully write the vast majority of my new code within the constraints of the new Fortran 90 constructs, to obtain maximum portability between different computers. However, as a scientist, I want to understand the workings of my experimental apparatus (the computer performing numerical simulations), and at times tune it for maximum performance. This may require a little more control over physical layout of my data structure.

The first step in using Fortran pointers is to determine the variables to which you will need to associate pointers. They must be given the TARGET attribute in a type statement. For example

      real, target :: a, b(1000), c(10,10)
integer, target :: i, j(100), k(20,20)
Why can't you associate Fortran pointers with any variable having the same type (as in C)? Maybe a cruel joke by the standards committee. More likely, another level of protection against programming errors, to limit the probability that a typo will associate a pointer with the wrong variable.

Now you define some pointers

      real, pointer :: pa, aptr, pb(:), pc1(:), pc2(:,:)
The type of the pointer must match the type of the intended target, and the rank of the pointer must match the rank of the portion of the target to which it will be associated. I use the term portion because we will find that "pb" or "pc1" can be associated with a row or a column in "c" for the above examples. The use of isolated colons for pointers is mandatory, because the actual size of the pointer object is not set until the pointer is associated with its target. A pointer behaves a lot like an allocated variable, except you are allocating specific existing memory rather than a new, unused piece of memory.

At any point in my program I can associate a pointer with a target with a statement such as:

      pa => a
After this statement I can write a normal assignment such as:

      b(i) = pa*c(i,i)
which is identical in effect to

      b(i) = a*c(i,i)
I could also write

      pa = 1.23456
print *, 'a = ', a
and have a printed result of "1.23456" because changing "pa" changes "a". They refer to the same location in the computer's memory.

The pointer can be re-associated with another variable at any time:

      pa => b(1)
or it can be forced to be disassociated from any variable with the NULLIFY statement:

      nullify (pa)
You can inquire if a pointer is associated with any target, by using the logical valued intrinsic function ASSOCIATED.

      if (associated(pa)) then
print *, 'pa is currently associated'
For more details you can use the optional TARGET argument to see if the pointer is currently associated with a specific target.

      if (associated(pa, target=a)) then
print *, 'pa is currently associated with "a"'
You can check to see if two pointers are associated with the same target using:

      if (associated(pa, target=aptr)) then
print *, 'pa and aptr point to the same target'
Look at the example associated.f and its results for more information on simple pointer association, the ASSOCIATED intrinsic function, and the NULLIFY statement.

Array Pointers

Now we get to the interesting part, making associations with arrays or portions of arrays. If I use the statement

      pb => b
Any reference to "pb" is identical to a reference to "b". "pb" effectively has 1000 elements. However, I can also write

      pb => b(101:200)
In this case "pb" is an array with only 100 elements, and pb(1) is the same as b(101). I could also do the following

      pb => c(5,1:10)
In this case we get the curious result that pb(1) is equivalent to c(5,1), pb(2) is equivalent to c(5,2), etc. If you think about this, sequential elements in the object "pb" are actually spaced 10 real words apart in memory. The compiler has hidden a skipping factor (stride) away in the definition of "pb" that can make your programming a little simpler than dealing with "c" directly.

Similar memory strides are involved when a rank two pointer is used to pick up a rectangular portion of a rank 2 array. The statement

      pc2 => c(3:5, 4:6)
results in pc2 behaving as an array with 3 rows and 3 columns ( real pc2(3,3)), for which element pc2(1,1) shares memory with c(3,4), pc2(2,1) is the same as c(4,4), on through pc2(3,3) with the same memory location as c(5,6).

A second method exists for allocating space to a pointer. You can use the ALLOCATE statement just as you would for an ALLOCATABLE array. The statement

      allocate (pc1(20))
results in addition of memory for a 20 element real array, that can only be accessed through use of pointer "pc1".

For more practice with array pointers, take a look at the results of pointers.f. and try some programs of your own. You are not likely to need pointers in the near future, but try to maintain some memory of this capability. It is very important for complex programming applications in just about any language.

Confused? This is the easy part. When pointers are combined with ALLOCATE and derived data types, you have an excellent way to create and manipulate linked lists. Jobs like sorting can be made a little simpler, and some very powerful data structures can be created.

Back to the Table of Contents / Home

Written and Maintained by John Mahaffy :