7 Pointers

 7.1 Pointer Registration and Conversion
 7.2 Allocating Exportable Dynamic Memory
 7.3 Accessing Dynamic Memory from C and FORTRAN
 7.4 Registering Your Own Pointers

FORTRAN 77 does not have the concept of a pointer. However, FORTRAN INTEGERs 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 (ints). 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 INTEGERs 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 INTEGERs (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 INTEGERs) is insufficient.

7.1 Pointer Registration and Conversion

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.

7.2 Allocating Exportable Dynamic Memory

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:

  F77_POINTER_FUNCTION(ralloc)( INTEGER(N) )
  {
     GENPTR_INTEGER(N)
  
  /* Allocate the memory and return the converted pointer. */
     return cnfFptr(cnfMalloc(*N*sizeof(F77_REAL_TYPE)));
  }

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:

  F77_SUBROUTINE(rfree)( POINTER(FPNTR) )
  {
     GENPTR_POINTER(FPNTR)
  
  /* Convert back to a C pointer and then free it. */
     cnfFree(cnfCptr(*FPNTR));
  }

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:

For convenience, cnfFree is also able to free pointers which have not been registered, in which case it behaves exactly like free.

7.3 Accessing Dynamic Memory from C and FORTRAN

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:

  F77_SUBROUTINE(rprint)( INTEGER(N), POINTER(FPNTR) )
  {
     GENPTR_INTEGER(N)
     GENPTR_POINTER(FPNTR)
     F77_REAL_TYPE *cpntr;
  
  /* Convert to a C pointer of the required type. */
     cpntr=(F77_REAL_TYPE)cnfCptr(*FPNTR);
  
  /* Access the data. */
     for(i=0;i<*N;i++) printf("%g\n",cpntr[i]);
  }

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:

        INCLUDE ’CNF_PAR’
  
        ...
  
        CALL RWRITE(N,%VAL(CNF_PVAL(PNTR)))

and the RWRITE routine could then access the array of values as follows:
        SUBROUTINE RWRITE( N, RDATA )
        INTEGER I, N
        REAL RDATA( N )
        DO 1 I = 1, N
           WRITE(*,*) RDATA(I)
      1 CONTINUE
        END

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.

7.4 Registering Your Own Pointers

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.