FORTRAN 77 does not have the concept of a pointer. However, FORTRAN INTEGER
s are widely used
in Starlink software as a replacement for pointers when passing the address of a data array from one
routine to another. Typically, a FORTRAN program calls a subroutine that returns a value in an
INTEGER
variable that represents the address of an array, which will usually have been dynamically
allocated. The value of this variable (as opposed to its address) is then passed on to another routine
where the contents of the array are accessed.
C, of course, does provide pointers – in fact you can hardly avoid using them – and they are distinct
from C integers (int
s). To take account of this, a macro POINTER
is defined to declare C function
arguments that the calling FORTRAN program declares as an INTEGER
but will actually treat as a
pointer. The FORTRAN routine should not process a POINTER
variable in any way. The
only valid operations it may perform are to copy it, to pass it to a subprogram using the
normal parameter passing mechanism, or to pass its value to a subprogram using the %VAL
facility (as described in Section 7.3) in order to access the contents of the array to which it
points.
Unfortunately, this scheme of using FORTRAN INTEGER
s to hold pointer values only works cleanly if
the length of an INTEGER
is the same as the length of the C generic pointer type void*
. Where
this is not the case (on DEC Alphas for instance), some way around the problem has to be
found.
On some systems, the linker may have flags to control the size of the address space in which a
program runs, and this can provide a simple solution. For example, although addresses on DEC
Alphas are normally 64 bits long (the length of a C pointer), it is possible to force programs to use
addresses in which only the lowest 32 bits have non-zero values. It then becomes a simple
matter to convert C pointers into FORTRAN INTEGER
s (which are 32 bits long) because
discarding the most significant bits has no effect. The standard Starlink link scripts on
DEC Alpha systems supply the necessary command-line flags to produce this behaviour
automatically.
However, this simple solution is not always applicable. Apart from the possibility that future 64-bit operating systems (of which there are likely to be an increasing number) may not provide this option of running programs in “lower memory”, even on those that do the option cannot always be exercised. For example, some software packages make use of “dynamic loading” of routines stored in shareable libraries as a way of allowing their capabilities to be extended. This is a very flexible facility, but it means that the loaded routines must execute in the address space of the main program, which means that the writer of the shareable library no longer has any control over the number of bits used in pointers. The only option is then to re-build the main software package with the required linker options. This is at best inconvenient, but in the case of commercial software packages it may be impossible.
There is also an increasing likelihood that programs may need to access data arrays of such size that
32 bits of address space (the usual length of FORTRAN INTEGER
s) is insufficient.
To overcome these problems, some method is needed of converting between (say) 64-bit C pointers
and the typical 32 bits of a FORTRAN INTEGER
. The same method must also work if the two pointer
representations are actually of equal length. To allow this, CNF maintains an internal table
which contains all the C pointers which will be exported and used from FORTRAN. The
pointers stored in this table are said to have been “registered” for use from both C and
FORTRAN.
When converting a C pointer into a FORTRAN pointer, it is sufficient simply to mask out all bits
except those that will fit into a FORTRAN INTEGER
. This is performed by the function cnfFptr
. When
converting in the opposite direction, the internal table must be searched to locate a pointer which has
the same value stored in the set of masked bits (e.g. the lowest 32 bits) as the FORTRAN pointer
value. The full value of the C pointer can then be read from the table. This conversion is performed by
cnfCptr
.
Apart from the requirement that all pointers which will be used from both C and FORTRAN must be
registered by entering them in the internal table, this scheme also requires that all registered pointers
should be unique in their lowest 32 bits (or whatever length a FORTRAN INTEGER
has) in
order for the conversion from FORTRAN to C to select a unique pointer from the table. In
practice, these requirements are most easily fulfilled by providing a set of memory allocation
functions in CNF which mirror the standard C run time library functions malloc
, calloc
and
free
.
The CNF functions cnfMalloc
and cnfCalloc
should be used whenever you wish to dynamically
allocate memory in a C function and export the resulting pointer for use from FORTRAN. You might
also want to use them if you are writing a subroutine library that returns pointers to dynamic memory
through its public interface, since the caller might then decide to pass these pointers on to a
FORTRAN routine.
For example, here is how you should allocate space for an array of N
FORTRAN REAL
values in a C
function and pass back the resulting pointer to FORTRAN:
When the allocated memory is no longer required, it should be freed using cnfFree
. This is how you
might import the FORTRAN pointer value allocated above back into C in order to free
it:
Externally, these CNF memory allocation functions behave exactly like their standard C equivalents
malloc
, calloc
and free
. Internally, however, they perform two important additional
functions:
cnfFptr
and cnfCptr
can operate (if you use malloc
to obtain a pointer, for instance, then
these conversion functions will fail and return zero).
INTEGER
) are unique, so that conversion between FORTRAN and
C pointer values is a well-defined operation.For convenience, cnfFree
is also able to free pointers which have not been registered, in which case it
behaves exactly like free
.
Of course, exchanging pointers to dynamic memory between C and FORTRAN is only part of the story. We must also be able to access the memory from both languages.
When importing a FORTRAN pointer into C, the first step is to use cnfCptr
to convert it to a C pointer
of type void*
. You can then use a cast to convert to the appropriate C pointer type (which you must
know in advance) in order to access the values stored in the memory. For example, to print out the
contents of a dynamically allocated array of FORTRAN REAL
data from a C function, you might use
the following:
Accessing dynamically allocated memory via a pointer from FORTRAN requires two steps. First, the
pointer value stored in a FORTRAN INTEGER
must be expanded to its full value (if necessary), equivalent
to the full equivalent C pointer. This value must then be turned into a FORTRAN array which can be
accessed. This requires that the pointer be passed to a separate FORTRAN routine using the %VAL
facility.1
For example, to convert a pointer into a REAL
array, you might call an auxiliary routine RWRITE
as
follows:
RWRITE
routine could then access the array of values as follows: Note how the argument of the %VAL
directive is CNF_PVAL(PNTR)
. The FORTRAN-callable CNF_PVAL
function serves to expand the pointer value out to its full length (equivalent to calling cnfCptr
from
C). The data type returned by this function will depend on the length of C pointers on the machine
being used and may not be a standard FORTRAN type (for instance, on DEC Alphas it is an
INTEGER*8
function). However, the data type declaration for this function is encapsulated in the
CNF_PAR
include file, so you need not include non-standard type declarations directly in your own
software.
CNF also provides two functions, cnfRegp
and cnfUregp
, for registering and un-registering pointers –
i.e. for entering and removing them from the internal table which is used for pointer conversion
between C and FORTRAN. You will probably never need to use these, since pointer registration is
normally managed completely automatically by the memory allocation functions which CNF
provides.
The reason for providing them is that there may be ways of creating new memory, for
which cnfMalloc
or cnfCalloc
cannot be used. For example, mapping data files directly
into memory. If the resulting C pointers are to be exported to FORTRAN, they must be
accessible to CNF for conversion purposes, so they must be registered in CNF’s internal
table.
If you should ever need to use this facility, then the main point to note is that attempting to register a
C pointer can potentially fail (cnfRegp
returns -1 to indicate this). This will occur if, when the C
pointer is converted to a FORTRAN INTEGER
, it clashes with a FORTRAN pointer value which is
already in use. In such a case you cannot safely export your pointer to FORTRAN, so you must obtain
a new pointer and re-register it. Typically, this may involve allocating a new block of memory at a
different location and freeing the original. The consolation is that such clashes are extremely
rare.
1This is a non-standard facility which originated in VAX FORTRAN but is now available on most FORTRAN compilers. It
is a compiler directive, rather than a function, and works by instructing the compiler to pass the argument by value rather
than by address. The routine receiving this argument is then tricked into thinking that it has received an
array starting at the address given by the pointer value. There are other ways of achieving this effect, such as
by addressing an array outside of its bounds, but the %VAL
method is the one most widely used in Starlink
software.