Fortran/memory management

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Introduction and historical background[edit | edit source]

Most Fortran programs prior to the Fortran90 standard used self-contained data, without structures, and without much in the way of shared, structured data. However, it was possible to share data, in structured and unstructured ways, using common blocks. Furthermore, there used to be little memory management going on in a Fortran program. Until Fortran90 allocated storage wasn't even possible, except via certain extensions (e.g. Cray pointers). Modern Fortran, however, supports many modern programming paradigms, has full support for allocatable data (including allocatable types), and allows for the use of pointers.

Shared variables in modules[edit | edit source]

Since Fortran90, shared variables are conveniently managed by the use of modules. Common blocks were used to define global memory prior to the Fortran90 standard; their use in modern Fortran is discouraged. A Fortran module can also contain subroutines and functions, but we shall leave the discussion of these features for later. As for the management of shared variables, they may be defined in a module:

module shared_variables
    implicit none
    private
    integer, public, save :: shared_integer
    integer, public, save :: another_shared_integer
    type, public :: shared_type
        logical :: my_logical
        character :: my_character
    end type shared_type
    type (shared_type), public :: shared_stuff
end module shared_variables

Note that it is considered good practice to declare any module private, even if it contains only public variables. Although save is the default for a variable in a module, meaning that it retains its previous value whenever the variables within the modules are used, it is sometimes considered good practice to make this explicit. The module can then be used in the main program:

program my_example
    use shared_variables, only: shared_integer, shared_stuff
    implicit none
    integer :: some_local_integer

    ! This will work and assign shared_integer to some local variable.
    shared_integer = some_local_integer
    ! This will print the component my_character from type shared_stuff
    ! to stdout.
    write (*,*) shared_stuff%my_character
    ! This, however, will not work, since another_shared_integer was not
    ! imported from the module - the program will not compile.
    shared_integer = another_shared_integer
end program my_example

Common blocks[edit | edit source]

Common blocks have been replaced by the use of public variables in modules in modern Fortran standards (Fortran90 and later). They are, however, historically important due to their use in older Fortran standards (77 and prior). A common block was Fortran's way of using shared, common storage for standards prior to Fortran90. In its simplest form, a common block is a way of defining global memory. Be careful, though. In most languages, each item in common memory is shared as a globally known name separately. In Fortran, however, the common block is a shared thing. I'll show several examples, but each example will share i and another_integer, and my_array, a 10x10 array of real numbers.

In C, for instance, I can define the shared memory using:

int i;
int another_integer;
float my_array[10][10];

and use these data elsewhere with:

extern float my_array[10][10];
extern int i;
extern int another_integer;

Note that one module declares the storage, and another uses the storage. Also note that the definitions and usages are not in the same order. This is because in C, as in most languages, i, another_integer, and my_array are all shared items. Not so in Fortran. In Fortran, all routines sharing this storage would have a definition something like this:

common i, another_integer, my_array
integer another_integer
real my_array(10,10)

This common block is stored as a block of data, as a linkable named structure. The only problem is that we don't know its name. Various compilers will give various names to this block. In some systems, the block actually doesn't have a name. We can avoid this problem by giving the structure a name. For instance,

common /my_block/ i, another_integer, my_array
integer another_integer
real my_array(10,10)

Using this form, two different Fortran programs can identify the same area of storage and share it, without having to know the structure of all shared storage. Also using this format, a C or other program could share the storage. For instance, a C program wanting to share this storage would declare the same storage as follows:

extern struct {
    int i;
    int another_integer;
    float my_array[10][10];
} my_block;

In the above example, having the my_block names match is critical, as well as having the types, sizes, and order match. However, having the names internally match is not since these names are known only locally. Also note that in the above example, Fortran's my_array(i,j) matches C's my_block.my_aArray[j][i].

Byte alignment[edit | edit source]

Byte alignment of intrinsic data types can mostly be ensured simply by using the appropriate kind. Fortran does not have any way of automatically ensuring derived data types are byte aligned. However, it is quite simple for the programmer to ensure that appropriate padding for data is inserted. For example, let's say we have a derived type that contains a character and an integer

type :: my_type
    integer (kind=4) :: ival
    character (len=1) :: letter
end type

Arrays of this type will have elements of size 5 bytes. If we want the elements of an array of this type to align every 8 bytes we need to add 3 more bytes of padding. We can do this by adding characters that serve no other purpose than as padding.

type :: my_type
    integer (kind=4) :: ival
    character (len=1) :: letter
    character (len=3) :: padding
end type

Memory management with pointers[edit | edit source]

In Fortran one can use pointers as some kind of alias for other data, e.g. such as a row in a matrix.

Pointer states[edit | edit source]

Each pointer is in one of the following states

  • undefined: right after definition if it has not been initialized
  • defined
    • null/not associated: not the alias of any data
    • associated: alias of some data.

The intrinsic function associated distinguished between the second and third states.

Assignments[edit | edit source]

Overview[edit | edit source]

We will use the following example: Let a pointer ptr be the alias of some real value x.

real, target :: x
real, pointer :: ptr
ptr => x

For the next example we will use a real matrix matr as target and the pointer ptr should alias a specific row.

real, dimension (4, 4), target :: matr
real, dimension (:), pointer :: ptr
ptr => matr(2, :)

Pointers can also be appointed to other pointers. This causes them to be an alias of the same data that the first pointer is. See the example below.

real, target :: x
real, pointer :: ptr1, ptr2
ptr1 => x
ptr2 => ptr1

Ordinary vs. pointer assignments[edit | edit source]

The difference between ordinary and pointer assignments of pointers can be explained by the following equalities. Assume this setup

real, target :: x1, x2
real, pointer :: ptr1, ptr2
ptr1 => x1
ptr2 => x2

Ordinary assignments of pointers lead to assignments of the data they point to. One can see this by the following two statements which are equal.

! Two equal statements
ptr1 = ptr2
x1 = x2

In contrast, pointer assignments changes the alias of one of the pointers and no change on the underlying data. See the equal example statements.

! Two equal statements
ptr1 => ptr2
ptr1 => x2

Memory allocation[edit | edit source]

After definition of pointers one can allocate memory for it using the allocate command. The memory pointed to by a pointer is given free again by the deallocate command. See the following example.

program main
    implicit none
    real, allocatable :: ptr

    allocate (ptr)
    ptr = 1.
    print *, ptr
    deallocate (ptr)
end program main

Examples[edit | edit source]

Allocatable vs. pointer[edit | edit source]

You can declare an array to have a known number of dimensions, but an unknown size using allocation:

real, dimension (:,:), allocatable :: my_array
allocate (my_array(10,10))
deallocate (my_array)

You can also declare something as a pointer:

real, dimension (:,:), pointer :: some_pointer
allocate (some_pointer(10,10))
deallocate (some_pointer)

In archaic versions of FORTRAN (77 and before), you'd just have a big static array and use whatever portion of it you need.