Allocatable Arrays and Dynamic Memory

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-18. Every example was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and every example was also built and run with LLVM Flang 23; the output shown is real output, and where the two compilers differ the difference is shown. Examples that are deliberately wrong, or that use a feature a compiler rejects, are labelled as such and show the real diagnostics. The standard defines dynamic association in 9.7 (allocate in 9.7.1, nullify in 9.7.2, deallocate in 9.7.3, stat= in 9.7.4 and errmsg= in 9.7.5), the allocatable attribute in 8.5.3, deferred-shape arrays in 8.5.8.4, allocation in intrinsic assignment in 10.2.1.3, allocatable dummy arguments in 15.5.2.7, and the intrinsic procedures allocated and move_alloc in 16.9.13 and 16.9.147.

An allocatable variable is a variable whose storage is requested during execution and returned by the language itself, not by the programmer’s discipline. That one property replaces most of what a C programmer does with malloc, realloc and free, and most of what a Fortran 77 programmer did with fixed upper bounds and work arrays passed down from the main program. In the running example, heatflow, the temperature field u(:,:) of grid_t is an allocatable component whose size is chosen from the input file, the Jacobi solver keeps an allocatable work array, and make_solver returns an allocatable polymorphic scalar. This page covers the attribute, the statements and intrinsics that act on it, the six situations in which the language changes an allocation status without a statement, the interaction with procedures and derived types, the choice between automatic arrays, allocatable arrays and pointers, and the diagnostics a compiler gives when the rules are broken. The shape and indexing rules of the arrays themselves are on Arrays Fundamentals; passing arrays to procedures is on Array Arguments: Assumed Shape, Size and Rank.

The allocatable attribute

A variable with the allocatable attribute is "a variable for which space is allocated during execution" (8.5.3). An allocatable array is a deferred-shape array (8.5.8.4): its rank is fixed in the declaration, written with one colon per dimension or, in Fortran 2023, with a rank clause (see The rank clause (Fortran 2023)), and its bounds are not part of the declaration. The bounds, the extents and, for a deferred-length character or a parameterized derived type, the length type parameters are established by the allocate statement or by an assignment, and can be different the next time.

real(dp), allocatable :: u(:, :)        ! rank-2 array: shape unknown until allocated
real(dp), allocatable :: t0             ! allocatable scalar
character(:), allocatable :: label      ! deferred-length character scalar
type(grid_t), allocatable :: grids(:)   ! array of a derived type
class(solver_t), allocatable :: s       ! polymorphic scalar: dynamic type chosen at allocation

Only variables and components can have the attribute. The result of referencing a function whose result variable is allocatable is a value that is not itself allocatable (note to 8.5.3). The attribute can be given in the type declaration statement, as above, or by a separate allocatable statement (8.5.3 and 8.6.2).

Where it can appear Example What the language does for you

Local variable or module variable (array)

real(dp), allocatable :: work(:)

Unallocated at the start of every invocation (8.5.3 and 9.7.1.3); deallocated on return unless it has the save attribute.

Scalar

real(dp), allocatable :: t0

The same rules as an array. Useful for large objects, polymorphic scalars and to express "absent" (see Unallocated actual arguments and optional dummy arguments).

Component of a derived type

real(dp), allocatable :: u(:, :) inside type :: grid_t

Unallocated in a new object; deallocated when the object is; copied element by element (deep copy) by intrinsic assignment (10.2.1.3).

Dummy argument

real(dp), allocatable, intent(inout) :: u(:, :)

Lets the callee allocate, reallocate and deallocate the caller’s variable. Requires an explicit interface (15.4.2.2).

Function result

real(dp), allocatable :: x(:) as the result variable

The caller needs no size in advance. Requires an explicit interface (15.4.2.2).

Deferred-length character

character(:), allocatable :: label

The length is whatever the last allocation or assignment gave it.

Polymorphic variable

class(solver_t), allocatable :: s, class(*), allocatable :: any

The dynamic type is set by allocate with a type-spec or source=.

Coarray

real, allocatable :: c(:)[:]

Allocation and deallocation synchronize all images of the current team (9.7.1.2). See Coarrays, Images and Synchronization.

Named constants, pointer entities, entities in a common block (C8123) and automatic arrays cannot be allocatable. Whether an allocatable variable is a good choice, compared with an automatic array or a pointer, is the subject of Automatic arrays, stack storage and the heap and Allocatable or pointer.

The lifecycle of an allocatable variable

At any time an allocatable variable has one of two allocation statuses (9.7.1.3): allocated or unallocated. The function allocated(x) (16.9.13) is true in the first case and false in the second. An unallocated variable has no shape and no value, so it "shall not be referenced or defined" (9.7.1.3 and 8.5.8.4); the only things a program may do with it are to allocate it, to ask whether it is allocated, to pass it to an allocatable dummy argument or to an optional dummy argument that will then be absent, and to give it a value by intrinsic assignment (which allocates it). The diagram below shows every way the status changes. Every transition except the allocate and deallocate statements is something the language does on the programmer’s behalf, and each one is covered in its own section below.

stateDiagram-v2 [*] --> Unallocated: program start, or entry to a procedure or block for an unsaved local variable Unallocated --> Allocated: allocate, assignment, or move_alloc into it Allocated --> Allocated: assignment of a different shape (deallocate, then allocate) Allocated --> Allocated: move_alloc into it from another variable Allocated --> Unallocated: deallocate Allocated --> Unallocated: end of scope for an unsaved local variable Allocated --> Unallocated: intent(out) allocatable dummy argument, on entry Allocated --> Unallocated: move_alloc out of it Allocated --> Error: allocate without stat= Unallocated --> Error: deallocate without stat= Unallocated --> Undefined: referenced or defined Error --> [*]: error termination

The initial state is unallocated "at the beginning of execution of a program" and at the beginning of each invocation of a procedure for an unsaved local variable, and at the beginning of each execution of a construct for an unsaved local variable of the construct (9.7.1.3). The Error state is reached only when no stat= specifier is present; with stat=, the failing statement leaves the status as it was and stores a nonzero code (9.7.4). The Undefined state stands for referencing or defining an unallocated variable: that is not an "error condition" the processor must detect, it is simply not allowed, and what happens is up to the compiler and the checking options (see Runtime errors and diagnostics).

The table lists each transition with the clause that governs it.

Event Status before Status after Clause

Start of the program, or entry to a procedure or block with an unsaved local variable

(none)

unallocated

9.7.1.3

allocate (x(n))

unallocated

allocated

9.7.1

allocate of an allocated variable

allocated

error condition

9.7.1.3

deallocate (x)

allocated

unallocated

9.7.3

deallocate of an unallocated variable

unallocated

error condition

9.7.3.2

Intrinsic assignment x = expr

unallocated, or allocated with a different shape or length

allocated with the shape and length of expr

10.2.1.3

return, end of a procedure, or end block, for an unsaved local variable that is not a function result

allocated

unallocated (deallocated automatically)

9.7.3.2

Invocation with an allocated actual argument for an intent(out) allocatable dummy argument

allocated

unallocated (deallocated on entry)

9.7.3.2, 15.5.2.7

call move_alloc (from, to)

from allocated; to either

to allocated with the contents of from; from unallocated

16.9.147

Intrinsic assignment to a variable of derived type

components allocated

allocatable components deallocated first, then reallocated by the assignment

9.7.3.2, 10.2.1.3

Deallocation of a variable of derived type

components allocated

every allocated allocatable component deallocated

9.7.3.2

The allocate statement

The allocate statement (R929, 9.7.1) creates allocatable variables and pointer targets:

allocate ([type-spec ::] allocation-list [, alloc-opt-list])
! allocation is   allocate-object [(allocate-shape-spec-list)] [lbracket allocate-coarray-spec rbracket]
! allocate-shape-spec is   [lower-bound-expr :] upper-bound-expr
! alloc-opt is one of   stat=stat-variable   errmsg=errmsg-variable   source=source-expr   mold=source-expr

Rules that matter in practice (9.7.1.1 and 9.7.1.2):

  • Each allocate-object is an allocatable variable or a data pointer (C936). It may be a component, as in allocate (g%u(nx, ny)), but not a coindexed object (C955).

  • An array needs its bounds, either as an allocate-shape-spec-list or from the shape of a source= expression of the same rank (C943). The bounds expressions are evaluated once, and later changes to the variables in them do not change the array. A missing lower bound is 1. If an upper bound is less than the lower bound, the extent is zero and the array has zero size, but it is allocated.

  • Several variables may be allocated in one statement. allocate (x(n), b(-3:m, 0:9), stat=ierr) is the form shown in the standard. The order in which they are allocated is not specified, and an allocate-object or a bound must not depend on another allocate-object, or on stat or errmsg, in the same statement.

  • No alloc-opt may appear twice (C948), and at most one of source= and a type-spec may appear (C949).

  • Allocating a variable that is already allocated is an error condition. This is the opposite of what malloc does and is the usual first runtime error a new Fortran programmer meets.

  • A scalar allocatable has no shape specification: allocate (t0) (C944).

  • The content of a new allocation is undefined, except for the default initialization of a derived type or source=. Only allocation and assignment, never allocate alone, give an array values.

The first example walks an array through its states and shows the bounds, the zero-size case and an allocatable scalar.

program allocation_basics
  use, intrinsic :: iso_fortran_env, only: real64, int32
  implicit none
  integer, parameter :: dp = real64
  real(dp), allocatable :: u(:, :)      ! deferred-shape rank-2 array, unallocated at start
  real(dp), allocatable :: t0           ! allocatable scalar
  integer(int32) :: nx, ny

  nx = 5_int32
  ny = 4_int32
  print '(a, l1)', 'allocated(u) at start:      ', allocated(u)

  allocate (u(nx, ny))                  ! bounds 1:nx, 1:ny
  u = 0.0_dp
  print '(a, l1, a, 2i3, a, 2i3, a, 2i3)', 'allocated(u) after allocate: ', allocated(u), &
    '  shape=', shape(u), '  lbound=', lbound(u), '  ubound=', ubound(u)
  deallocate (u)
  print '(a, l1)', 'allocated(u) after deallocate: ', allocated(u)

  allocate (u(0:nx - 1, -1:ny - 2))     ! explicit lower bounds
  print '(a, 2i3, a, 2i3)', 'lbound=', lbound(u), '  ubound=', ubound(u)
  deallocate (u)

  allocate (u(3, 0))                    ! upper bound < lower bound: zero-sized but allocated
  print '(a, l1, a, i0, a, 2i3)', 'zero-sized: allocated=', allocated(u), '  size=', size(u), '  shape=', shape(u)

  allocate (t0, source=300.0_dp)        ! allocatable scalar, value from source=
  print '(a, f6.1)', 't0=', t0
end program allocation_basics

Output (identical with Flang 23):

allocated(u) at start:      F
allocated(u) after allocate: T  shape=  5  4  lbound=  1  1  ubound=  5  4
allocated(u) after deallocate: F
lbound=  0 -1  ubound=  4  2
zero-sized: allocated=T  size=0  shape=  3  0
t0= 300.0

The stat= and errmsg= specifiers

Without stat=, any error condition during allocate or deallocate initiates error termination (9.7.1.2 and 9.7.3.1), so a failed allocation ends the program with a message. With stat=, the statement sets the integer variable to zero on success and to a positive processor-dependent value on failure (9.7.4), and errmsg= receives an explanatory message "as if by intrinsic assignment" (9.7.5). The set of error conditions is processor dependent: it includes at least an allocation that cannot be satisfied, allocating an allocated variable and deallocating an unallocated one. The status of an object whose allocation failed is "processor dependent" if other objects were also in the statement (9.7.4): each object that was successfully allocated stays allocated, each that was not retains its previous status. Check them individually with allocated.

Because the numbers are processor dependent, a program that wants portable behavior tests stat /= 0, never a particular value, and stat_failed_image and stat_stopped_image from iso_fortran_env are the only standard values (9.7.4).

program allocation_errors
  use, intrinsic :: iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: dp = real64
  real(dp), allocatable :: u(:, :)
  integer :: stat
  character(len=200) :: msg                 ! fixed length: the message is truncated or blank padded
  integer(int64) :: n_big

  allocate (u(10, 10), stat=stat, errmsg=msg)
  print '(a, i0)', 'first allocate: stat=', stat

  allocate (u(10, 10), stat=stat, errmsg=msg)   ! already allocated: an error condition
  print '(a, i0, 2a)', 'second allocate: stat=', stat, ' errmsg=', trim(msg)

  deallocate (u, stat=stat, errmsg=msg)
  print '(a, i0)', 'first deallocate: stat=', stat

  deallocate (u, stat=stat, errmsg=msg)         ! already unallocated: an error condition
  print '(a, i0, 2a)', 'second deallocate: stat=', stat, ' errmsg=', trim(msg)

  n_big = 2_int64**45                           ! 2**46 elements of 8 bytes: far beyond any memory
  allocate (u(n_big, 2_int64), stat=stat, errmsg=msg)
  print '(a, i0, 2a)', 'huge allocate: stat=', stat, ' errmsg=', trim(msg)
  print '(a, l1)', 'u allocated after the failure: ', allocated(u)
  if (stat /= 0) print '(a)', 'recovering: the program is still running'
end program allocation_errors

Output with GNU Fortran 16.2:

first allocate: stat=0
second allocate: stat=5014 errmsg=Attempt to allocate an allocated object
first deallocate: stat=0
second deallocate: stat=1 errmsg=Attempt to deallocate an unallocated object
huge allocate: stat=5020 errmsg=Insufficient virtual memory
u allocated after the failure: F
recovering: the program is still running

The same program with LLVM Flang 23 gives the same structure and different numbers and texts, which is exactly why portable code must not depend on them:

first allocate: stat=0
second allocate: stat=12 errmsg=Base address is not null
first deallocate: stat=0
second deallocate: stat=11 errmsg=Base address is null
huge allocate: stat=19 errmsg=Memory allocation failed
u allocated after the failure: F
recovering: the program is still running

A failed allocate leaves the variable unallocated and the program running, which makes stat= the right tool for the one allocation whose size depends on user input, such as the field size read from the heatflow namelist. For small allocations whose failure is unrecoverable anyway, leaving out stat= gives the shortest code and the clearest message.

Deferred-length errmsg (Fortran 2023)

Before Fortran 2023 the errmsg= variable had to be a fixed-length character variable, so the message was truncated or padded. Fortran 2023 allows it to be a deferred-length allocatable character, which "is allocated by the processor to the length of the explanatory message" (9.7.5, and the list of new features in the Introduction). The same rule applies to iomsg= (see Error Handling and Program Termination). The next program is a deliberate test of that feature and does not work with the compilers checked here:

program deferred_errmsg
  implicit none
  real, allocatable :: a(:)
  character(:), allocatable :: msg    ! Fortran 2023: errmsg-variable may be deferred length
  integer :: stat

  allocate (a(3))
  allocate (a(3), stat=stat, errmsg=msg)    ! error condition: a is already allocated
  print '(a, i0)', 'stat=', stat
  if (allocated(msg)) then
    print '(a, i0, 2a)', 'len(msg)=', len(msg), ' msg=', msg
  else
    print '(a)', 'msg was left unallocated'
  end if
end program deferred_errmsg

GNU Fortran 16.2 compiles it with a warning, and the program crashes when the error condition occurs:

deferred_errmsg.f90:8:85:

    8 |   allocate (a(3), stat=stat, errmsg=msg)    ! error condition: a is already allocated
      |                                                                                     ^
Warning: '.msg' may be used uninitialized [-Wmaybe-uninitialized]
deferred_errmsg.f90:1:23:

    1 | program deferred_errmsg
      |                       ^
note: '.msg' was declared here

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

(backtrace omitted)
Segmentation fault

LLVM Flang 23 compiles and runs it but does not implement the rule: the message is not stored.

stat=12
msg was left unallocated

Until both implement the feature, declare errmsg with a fixed length (character(len=200)) and use trim. The crash happens only when an error condition occurs, so a program with a deferred-length errmsg passes every test of the successful path; test the failing path too.

Initializing an allocation: source=, mold= and a type-spec

Three forms of allocate give an allocation more than a shape:

  • source=expr allocates with the dynamic type, type parameters, shape and lower bounds of expr and copies its value (9.7.1.2). The expression is evaluated exactly once. It must be conformable with the allocation: a scalar source is broadcast to every element of an array allocation, and an array source must have the same rank as the allocate-object. If expr is allocatable it must be allocated, and if it is a pointer it must be associated.

  • mold=expr takes the same type, type parameters and, for an array, the same shape and bounds, but does not copy a value; the variable used as a mold need not even be defined. It is the cheap way to say "another one like this".

  • A type-spec before the list, allocate (character(len=n) :: label), gives the type explicitly. It is required when an allocate-object has a deferred type parameter, is unlimited polymorphic or is of an abstract type, unless source= or mold= is present (C937). The kind type parameters must match those of the allocate-object (C940), and if the declaration gives a nondeferred length, the type-spec length must equal it, or an error condition occurs.

program source_and_mold
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  integer :: i
  real(dp) :: field(0:3, 2)
  real(dp), allocatable :: copy(:, :), shape_only(:, :), typed(:), zeros(:)
  character(:), allocatable :: label, trimmed
  character(len=12) :: name = 'heatflow'

  field = reshape([real(dp) :: (real(i, dp), i=1, 8)], [4, 2])

  ! source=: same shape, lower bounds and values as the source expression
  allocate (copy, source=field)
  print '(a, 2i3, a, 2i3, a, f5.1)', 'source: lbound=', lbound(copy), ' ubound=', ubound(copy), ' copy(3,2)=', copy(3, 2)

  ! mold=: shape, type and type parameters only; the value is NOT copied
  allocate (shape_only, mold=field)
  print '(a, 2i3, a, 2i3)', 'mold:   lbound=', lbound(shape_only), ' ubound=', ubound(shape_only)

  ! a scalar source= initializes every element of an array allocation
  allocate (zeros(6), source=-1.0_dp)
  print '(a, i0, a, f5.1)', 'scalar source: size=', size(zeros), ' sum=', sum(zeros)

  ! type-spec: required for a deferred-length character unless source= or mold= is given
  allocate (character(len=len_trim(name)) :: label)
  print '(a, i0)', 'after allocate: len(label)=', len(label)
  label = name                         ! intrinsic assignment reallocates to len(name) = 12
  print '(a, i0, 3a)', 'after label = name: len(label)=', len(label), ' [', label, ']'
  trimmed = trim(name)                 ! allocated on assignment with the trimmed length
  print '(a, i0, 3a)', 'trimmed: len=', len(trimmed), ' [', trimmed, ']'

  allocate (real(dp) :: typed(5))      ! the type-spec form also works for non-character types
  print '(a, i0)', 'typed size=', size(typed)
end program source_and_mold

Output (identical with Flang 23):

source: lbound=  0  1 ubound=  3  2 copy(3,2)=  8.0
mold:   lbound=  0  1 ubound=  3  2
scalar source: size=6 sum= -6.0
after allocate: len(label)=8
after label = name: len(label)=12 [heatflow    ]
trimmed: len=8 [heatflow]
typed size=5

Two details in the output are worth remembering. A source= allocation keeps the lower bounds of the source (here 0:3), while an allocation by assignment from an expression follows the same rule, as the section on automatic reallocation below shows. And label = name changed the length of label from 8 to 12: after a type-spec allocation of a deferred-length variable, a following intrinsic assignment reallocates it again to the length of the right-hand side, which here includes the four trailing blanks of name. Write label = trim(name) when the trailing blanks are not data.

For derived types, source= copies allocatable components too (a deep copy), which makes allocate (copy, source=original) the shortest way to clone a polymorphic object whose dynamic type is not known statically. See Object Oriented Programming for the polymorphic cases.

Bounds given by arrays (Fortran 2023)

Fortran 2023 lets an allocate statement specify the bounds with rank-one integer arrays instead of one scalar per dimension: allocate (a(extents)) where extents is an array of size equal to the rank, and allocate (b(lows:highs)) with both bounds as arrays (R933, C947, and "An ALLOCATE statement can specify the bounds of an array allocation with array expressions" in the Introduction). The point is rank-independent code, where the rank is not known to the programmer. Neither compiler checked here accepts the feature. This program is deliberately rejected:

program bounds_exprs
  implicit none
  real, allocatable :: a(:, :), b(:, :)
  integer :: extents(2), lows(2), highs(2)

  extents = [4, 3]
  lows = [0, -1]
  highs = [3, 1]
  allocate (a(extents))               ! Fortran 2023: array upper-bounds-expr
  print '(a, 2i3, a, 2i3)', 'a lbound=', lbound(a), ' ubound=', ubound(a)
  allocate (b(lows:highs))            ! lower-bounds-expr:upper-bounds-expr
  print '(a, 2i3, a, 2i3)', 'b lbound=', lbound(b), ' ubound=', ubound(b)
end program bounds_exprs

GNU Fortran 16.2:

bounds_exprs.f90:9:13:

    9 |   allocate (a(extents))               ! Fortran 2023: array upper-bounds-expr
      |             1
Error: Rank mismatch in array reference at (1) (1/2)
bounds_exprs.f90:11:14:

   11 |   allocate (b(lows:highs))            ! lower-bounds-expr:upper-bounds-expr
      |              1
Error: Array index at (1) must be scalar

LLVM Flang 23:

error: Semantic errors in bounds_exprs.f90
bounds_exprs.f90:9:13: error: The number of shape specifications, when they appear, must match the rank of allocatable object
    allocate (a(extents))               ! Fortran 2023: array upper-bounds-expr
              ^
bounds_exprs.f90:3:24: Declared here with rank 2
    real, allocatable :: a(:, :), b(:, :)
                         ^
bounds_exprs.f90:9:15: error: Must be a scalar value, but is a rank-1 array
    allocate (a(extents))               ! Fortran 2023: array upper-bounds-expr
                ^^^^^^^
bounds_exprs.f90:11:13: error: The number of shape specifications, when they appear, must match the rank of allocatable object
    allocate (b(lows:highs))            ! lower-bounds-expr:upper-bounds-expr
              ^
bounds_exprs.f90:3:33: Declared here with rank 2
    real, allocatable :: a(:, :), b(:, :)
                                  ^
bounds_exprs.f90:11:15: error: Must be a scalar value, but is a rank-1 array
    allocate (b(lows:highs))            ! lower-bounds-expr:upper-bounds-expr
                ^^^^
bounds_exprs.f90:11:20: error: Must be a scalar value, but is a rank-1 array
    allocate (b(lows:highs))            ! lower-bounds-expr:upper-bounds-expr
                     ^^^^^

Until the compilers catch up, write the scalar form allocate (a(extents(1), extents(2))) or use source= with a mold of the right shape. The same Fortran 2023 change allows arrays in pointer bounds remapping and in subscript triplets (9.5.3.2), described on Pointers and Targets.

The rank clause (Fortran 2023)

Fortran 2023 adds a rank clause (R829 and 8.5.17) so that a deferred-shape array can be declared as real, allocatable, rank(2) :: u instead of real, allocatable :: u(:, :). The argument is a constant expression, so the rank can be computed: the standard’s own example logical, rank(rank(x0)), allocatable :: x1 declares an allocatable of the same rank as x0. The clause is only allowed for dummy arguments and for entities with the allocatable or pointer attribute (C864).

program rank_clause
  implicit none
  real, allocatable, rank(2) :: u      ! Fortran 2023 rank clause: deferred-shape rank-2 array
  allocate (u(3, 2))
  u = 1.0
  print '(a, i0, a, 2i3)', 'rank=', rank(u), ' shape=', shape(u)
end program rank_clause

GNU Fortran 16.2 rejects it:

rank_clause.f90:3:8:

    3 |   real, allocatable, rank(2) :: u      ! Fortran 2023 rank clause: deferred-shape rank-2 array
      |        1
Error: Invalid character in name at (1)
rank_clause.f90:4:13:

    4 |   allocate (u(3, 2))
      |             1
Error: Allocate-object at (1) is neither a data pointer nor an allocatable variable
rank_clause.f90:5:3:

    5 |   u = 1.0
      |   1
Error: Symbol 'u' at (1) has no IMPLICIT type

LLVM Flang 23 accepts it:

rank=2 shape=  3  2

The deallocate statement and allocated

deallocate (x, y, stat=ierr, errmsg=msg) (R944 and 9.7.3.1) returns the storage of every listed allocatable variable and sets its status to unallocated. For a pointer it frees the target and disassociates the pointer. The rules mirror those of allocate:

  • Deallocating an unallocated allocatable is an error condition (9.7.3.2). With stat= the program continues, otherwise it terminates.

  • An allocatable variable shall not be deallocated if it, or any subobject of it, is argument associated with a dummy argument or construct associated with an associate name. Deallocating inside a procedure the actual argument that was passed by a non-allocatable dummy is therefore not allowed.

  • Deallocating an allocatable variable with the target attribute makes every pointer associated with it undefined.

  • The order in which several objects in one statement are deallocated is processor dependent.

  • Deallocation of a derived-type object deallocates its allocated allocatable subobjects, after any final subroutine of the object has run (9.7.3.2).

  • A deallocate never needs a guard in the common case: if (allocated(x)) deallocate (x) is correct, but code that relies on automatic deallocation (the next section) rarely needs the statement at all.

allocated (16.9.13) accepts an allocatable array or scalar and returns a default logical. It is one of the inquiry functions that an unallocated variable may be passed to. It is not defined for pointers: associated (see Pointers and Targets) is the pointer counterpart. A common idiom for "create on first use" is

if (.not. allocated(self%work)) allocate (self%work(nx, ny))

and Fortran 2003 and later allow the shorter form if (.not. allocated(self%work)) self%work = self%grid%u, which allocates and copies in one statement and is what the heatflow solvers use (heatflow_jacobi.f90 and heatflow_ftcs.f90).

Automatic deallocation

An allocatable variable cannot leak, because the language deallocates it. "The effect of automatic deallocation is the same as that of a deallocate statement without a dealloc-opt-list" (9.7.3.2), including the execution of any final subroutine before the storage goes away (7.5.6.3). It happens in these situations:

  1. When a procedure ends by return or end, every unsaved allocated local allocatable variable of the procedure is deallocated, except a function result and its subobjects, which keep their allocation for the caller.

  2. When a block construct ends, every unsaved allocated local allocatable variable of the construct is deallocated.

  3. After the innermost executable construct that contains a reference to a function with an allocatable result (or one with an allocated allocatable subobject) has been executed, the result is deallocated. The function result is a temporary.

  4. When a procedure is invoked with an allocated allocatable actual argument for an intent(out) allocatable dummy argument, the actual argument is deallocated on entry; the same holds for an allocated allocatable subobject of an actual argument that corresponds to an intent(out) dummy argument of any kind.

  5. When an intrinsic assignment is executed, any allocated allocatable subobject of the variable is deallocated before the assignment, and the allocatable variable itself is deallocated if the right-hand side has a different shape (see the next section).

  6. When a variable of derived type is deallocated, its allocated allocatable subobjects are deallocated, after the final subroutine of the variable has run. The same cascade happens when a variable with such a component is itself automatically deallocated.

Variables with the save attribute keep their allocation. In the main program, in a module and in a submodule, every variable has the save attribute implicitly (8.5.16), so an allocatable variable declared there lives until the program ends, and at the end it is not deallocated by finalization: "entities existing immediately prior to termination are not finalized" when a program ends with stop, error stop or end program (7.5.6.4). The operating system reclaims the memory, but a final subroutine that writes a file or closes a handle will not run for a main-program variable. Put such variables inside a block construct if the cleanup matters.

The program below makes each rule observable by giving the allocated object a final subroutine that prints a line when it runs.

module field_store
  implicit none
  private
  public :: tracker, scope_demo, keep_demo, make_tracked, reset_tracked

  type :: tracker                        ! prints a line when an object of this type is finalized
    character(len=8) :: tag = '?'
  contains
    final :: tracker_final
  end type tracker

contains

  subroutine tracker_final(self)
    type(tracker), intent(inout) :: self
    print '(2a)', '    final subroutine runs for: ', trim(self%tag)
  end subroutine tracker_final

  subroutine scope_demo()
    type(tracker), allocatable :: local            ! unsaved local: unallocated on every call
    type(tracker), allocatable, save :: kept       ! saved: keeps its status between calls
    print '(a, l1, a, l1)', '  on entry: allocated(local)=', allocated(local), ' allocated(kept)=', allocated(kept)
    allocate (local, source=tracker('local'))
    if (.not. allocated(kept)) allocate (kept, source=tracker('kept'))
    print '(a)', '  leaving scope_demo'
  end subroutine scope_demo

  subroutine keep_demo()
    block
      type(tracker), allocatable :: in_block
      allocate (in_block, source=tracker('in_block'))
      print '(a)', '  end of the block construct'
    end block
    print '(a)', '  after the block'
  end subroutine keep_demo

  function make_tracked(tag) result(r)             ! the result keeps its allocation
    character(len=*), intent(in) :: tag
    type(tracker), allocatable :: r
    allocate (r, source=tracker(tag))
  end function make_tracked

  subroutine reset_tracked(t)
    type(tracker), allocatable, intent(out) :: t   ! an allocated actual argument is deallocated on entry
    print '(a, l1)', '    inside reset_tracked: allocated(t)=', allocated(t)
  end subroutine reset_tracked
end module field_store

program auto_dealloc
  use field_store
  implicit none
  type(tracker), allocatable :: t

  print '(a)', 'first call of scope_demo:'
  call scope_demo()
  print '(a)', 'second call of scope_demo:'
  call scope_demo()

  print '(a)', 'block construct:'
  call keep_demo()

  print '(a)', 'function result, then intent(out):'
  t = make_tracked('result')            ! the function result is a temporary, deallocated after the statement
  print '(a, l1)', '  t allocated: ', allocated(t)
  call reset_tracked(t)

  print '(a)', 'end of the main program'
end program auto_dealloc

Output (identical with Flang 23):

first call of scope_demo:
  on entry: allocated(local)=F allocated(kept)=F
  leaving scope_demo
    final subroutine runs for: local
second call of scope_demo:
  on entry: allocated(local)=F allocated(kept)=T
  leaving scope_demo
    final subroutine runs for: local
block construct:
  end of the block construct
    final subroutine runs for: in_block
  after the block
function result, then intent(out):
    final subroutine runs for: result
  t allocated: T
    final subroutine runs for: result
    inside reset_tracked: allocated(t)=F
end of the main program

Read the output against the rules above. local is deallocated when scope_demo returns (rule 1), while kept, which has save, is still allocated on entry to the second call and is never finalized. in_block is deallocated at end block (rule 2), before the statement after the construct. The function result result is finalized as soon as the assignment statement is complete (rule 3), because t = make_tracked('result') copies it into t; the second "result" line is the copy held by t, which intent(out) deallocates on entry to reset_tracked, so allocated(t) is false inside the subroutine (rule 4). The final subroutine does not run for t or kept at the end of the main program.

A subtle consequence of rule 3: an allocatable function result is created and destroyed per reference. In a loop, s = norm(make_field(n)) allocates and frees the whole field at every iteration. Hoist the call or pass a work array when the field is large (see Performance).

Automatic (re)allocation on assignment

In Fortran 2003 and later, an intrinsic assignment x = expr to an allocatable variable can allocate or reallocate the variable. The rules are in 10.2.1.3 and are easy to state:

  1. If x is an unallocated allocatable array, expr must have the same rank, and x is allocated with the shape of expr.

  2. If x is allocated and expr is an array of a different shape, a character expression with a different length, or a polymorphic value of a different dynamic type, x is deallocated and then allocated again with the shape, length and type of expr.

  3. A newly allocated x has, for an array expr, "each lower bound equal to the corresponding element of lbound (expr)`", and the old bounds when `expr is a scalar.

  4. If x is allocated and expr has the same shape, nothing is allocated: the values are copied.

  5. The right-hand side is evaluated completely before the left-hand side changes, so x = [x, 99.0] is legal and reads the old x.

program reallocation_on_assignment
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp), allocatable :: x(:), history(:)
  real(dp) :: y(0:4), r
  integer :: step

  y = [10.0_dp, 20.0_dp, 30.0_dp, 40.0_dp, 50.0_dp]

  ! 1. assignment to an unallocated array allocates it with the shape of the expression
  x = y(1:3)
  print '(a, i0, a, i0, a, i0)', 'x = y(1:3):       size=', size(x), ' lbound=', lbound(x, 1), ' ubound=', ubound(x, 1)

  ! 2. a different shape reallocates; the new lower bound is lbound(expr)
  x = y
  print '(a, i0, a, i0, a, i0)', 'x = y:            size=', size(x), ' lbound=', lbound(x, 1), ' ubound=', ubound(x, 1)
  x = y(2:4)                            ! a section always has lower bound 1
  print '(a, i0, a, i0, a, i0)', 'x = y(2:4):       size=', size(x), ' lbound=', lbound(x, 1), ' ubound=', ubound(x, 1)

  ! 3. an array constructor has lower bound 1, a scalar keeps the current shape
  x = [x, 99.0_dp]                      ! the right-hand side is evaluated first, then x is reallocated
  print '(a, i0, a, f5.1)', 'x = [x, 99.0]:    size=', size(x), ' last=', x(size(x))
  x = 0.0_dp
  print '(a, i0, a, f5.1)', 'x = 0.0:          size=', size(x), ' x(1)=', x(1)

  ! 4. the "growing history" idiom: simple, but every step copies the whole array
  allocate (history(0))
  do step = 1, 5
    r = 1.0_dp/real(step, dp)
    history = [history, r]
  end do
  print '(a, i0, a, f6.4)', 'history: size=', size(history), ' last=', history(size(history))

  ! 5. x(:) = ... turns reallocation off: the shapes must already agree
  x(:) = 1.0_dp
  print '(a, i0)', 'x(:) = 1.0 keeps size ', size(x)
end program reallocation_on_assignment

Output (identical with Flang 23):

x = y(1:3):       size=3 lbound=1 ubound=3
x = y:            size=5 lbound=0 ubound=4
x = y(2:4):       size=3 lbound=1 ubound=3
x = [x, 99.0]:    size=4 last= 99.0
x = 0.0:          size=4 x(1)=  0.0
history: size=5 last=0.2000
x(:) = 1.0 keeps size 4

The output shows rule 3, which surprises people. The array constructor and the section y(1:3) have lower bound 1, but the whole array y was declared y(0:4), so x = y allocates x with lower bound 0. Code that subscripts x(1) afterwards reads the second element. The portable habit is either to pass x to a procedure with an assumed-shape dummy argument (which resets the lower bound to 1) or to assign x = y(:), since a section always has lower bound 1.

A section on the left-hand side switches reallocation off: x(:) = expr is an assignment to an existing array and the shapes must conform. That is occasionally useful to protect against accidental reallocation and always dangerous when x might be unallocated. This program is deliberately wrong:

! Deliberately wrong: a(:) = ... does not reallocate, so the shapes must already agree.
program section_assignment_mismatch
  implicit none
  real, allocatable :: a(:)
  a = [1.0, 2.0, 3.0]
  a(:) = [1.0, 2.0, 3.0, 4.0, 5.0]
  print *, a
end program section_assignment_mismatch

GNU Fortran 16.2 with -fcheck=all:

At line 6 of file section_mismatch.f90
Fortran runtime error: Array bound mismatch for dimension 1 of array 'a' (3/5)

LLVM Flang 23:

fatal Fortran runtime error(section_mismatch.f90:6): Assign: mismatching element counts in array assignment (to 3, from 5)
Aborted

Compiler flags for reallocation

Option Effect

-frealloc-lhs and -fno-realloc-lhs (GNU Fortran)

Enable and disable reallocation on assignment. It is on by default except with -std=f95. With -fno-realloc-lhs an assignment to a differently shaped array is a shape mismatch, as in Fortran 95.

-Wrealloc-lhs (GNU Fortran)

Warn where the compiler may insert reallocation code for an array of intrinsic type, as a hint to rewrite hot loops. Not part of -Wall.

-Wrealloc-lhs-all (GNU Fortran)

Also warn for scalars and derived types.

-frealloc-lhs and -fno-realloc-lhs (LLVM Flang)

The same switch, documented in the Flang command line reference. Reallocation happened in every example on this page without it.

Compiling realloc_lhs.f90 with -Wrealloc-lhs gives one warning for each of the five assignments that may reallocate; the first two are shown.

realloc_lhs.f90:12:3:

   12 |   x = y(1:3)
      |   1~
Warning: Code for reallocating the allocatable array at (1) will be added [-Wrealloc-lhs]
realloc_lhs.f90:16:3:

   16 |   x = y
      |   1~
Warning: Code for reallocating the allocatable array at (1) will be added [-Wrealloc-lhs]

The warnings are advice, not errors. An assignment x = y where the shapes are known to agree costs only a comparison of the shapes, but inside a hot loop the check can prevent vectorization, and a loop that grows an array with history = [history, r] copies the entire array every iteration.

move_alloc: transferring an allocation

call move_alloc (from, to [, stat, errmsg]) (16.9.147) moves an allocation from one allocatable variable to another without copying the data. On a successful call:

  • if to is allocated it is deallocated first;

  • if from is allocated, to becomes allocated with the dynamic type, type parameters, bounds and value that from had, and from becomes unallocated;

  • if from is unallocated, to ends up unallocated;

  • a pointer associated with from becomes associated with to if to has the target attribute, and becomes undefined otherwise;

  • from and to must have the same rank (and corank) and be type compatible, and if from is polymorphic so must be to; they must not be the same variable unless from is unallocated. to is intent(out) and so is deallocated on entry in any case.

It is a simple subroutine (F2023) unless from is a coarray, and a pure one in the earlier standards, so it can be called from pure procedures and from do concurrent. Its two best-known uses are growing an array without a second copy of the data and moving the result of a computation into a component or an output argument.

module residual_log
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp, log_t

  integer, parameter :: dp = real64

  type :: log_t                          ! growable list of residuals with spare capacity
    real(dp), allocatable :: values(:)
    integer :: n = 0
    integer :: grow_count = 0
  contains
    procedure :: append => log_append
  end type log_t

contains

  subroutine log_append(self, r)
    class(log_t), intent(inout) :: self
    real(dp), intent(in) :: r
    real(dp), allocatable :: bigger(:)
    if (.not. allocated(self%values)) allocate (self%values(4))
    if (self%n == size(self%values)) then
      allocate (bigger(2*size(self%values)))
      bigger(1:self%n) = self%values(1:self%n)
      call move_alloc(from=bigger, to=self%values)   ! hands the new block over without a second copy
      self%grow_count = self%grow_count + 1
    end if
    self%n = self%n + 1
    self%values(self%n) = r
  end subroutine log_append

end module residual_log

program move_alloc_demo
  use residual_log
  implicit none
  type(log_t) :: history
  real(dp), allocatable, target :: a(:), b(:)
  real(dp), pointer :: p(:)
  integer :: k, stat
  character(len=100) :: msg

  do k = 1, 100
    call history%append(1.0_dp/real(k, dp))
  end do
  print '(a, i0, a, i0, a, i0)', 'n=', history%n, ' capacity=', size(history%values), ' reallocations=', history%grow_count

  ! what move_alloc does to the two variables
  allocate (a(0:2))
  a = [1.0_dp, 2.0_dp, 3.0_dp]
  allocate (b(10))
  p => a
  call move_alloc(a, b, stat=stat, errmsg=msg)
  print '(a, l1, a, l1, a, i0)', 'a allocated=', allocated(a), '  b allocated=', allocated(b), '  stat=', stat
  print '(a, 2i3, a, 3f5.1)', 'b took the bounds of a: lbound/ubound=', lbound(b, 1), ubound(b, 1), '  values', b
  print '(a, l1, a, f5.1)', 'associated(p, b)=', associated(p, b), '  p(0)=', p(0)   ! b has TARGET, so p follows the move

  ! moving from an unallocated variable leaves the destination unallocated
  call move_alloc(a, b)
  print '(a, l1)', 'after moving the unallocated a: allocated(b)=', allocated(b)
end program move_alloc_demo

Output (identical with Flang 23):

n=100 capacity=128 reallocations=5
a allocated=F  b allocated=T  stat=0
b took the bounds of a: lbound/ubound=  0  2  values  1.0  2.0  3.0
associated(p, b)=T  p(0)=  1.0
after moving the unallocated a: allocated(b)=F

The reallocation count is the case for geometric growth. Appending 100 values to history with history = [history, r] would copy the array 100 times with every copy larger than the last, in total about 5,000 elements; the list with capacity doubling does five reallocations, each of which copies the used elements once (bigger(1:n) = values(1:n)) and then move_alloc gives the new block to the component with no further copy. Even this version copies once per growth; the standard offers no realloc that extends in place.

move_alloc makes the bounds of to equal to the bounds of from, as the second line of output shows (lbound 0), and the pointer p stays associated with the data because b has the target attribute. Without target on to, p would become undefined and any use of it would be an error. The compilers checked here do not diagnose that.

The optional stat and errmsg arguments are part of the Fortran 2023 text (they were not in Fortran 2008). They exist chiefly for coarray arguments, where a stopped or failed image is an error condition (stat_stopped_image and stat_failed_image). GNU Fortran 16.2 accepts them as keywords (stat=stat, errmsg=msg), as the example does, but its manual still lists only the arguments from and to, and in the test for this page a positional call move_alloc (a, b, stat, msg) crashed the compiler with an internal compiler error; LLVM Flang 23 accepted both forms. Use the keywords, or leave the arguments out.

Allocatable dummy arguments, function results and components

Allocatable dummy arguments

A dummy argument with the allocatable attribute is associated with an actual argument that is itself allocatable (15.5.2.7). The two must have the same type, rank and corank, the same declared type (and both polymorphic or neither), and an explicit interface is required (15.4.2.2), which in practice means that the callee lives in a module. The actual argument may be unallocated, and the callee may allocate, reallocate, deallocate and test it; because the dummy argument is the actual argument, the caller sees every change. The intent decides what is allowed:

Intent Meaning for an allocatable dummy argument

intent(in)

Neither the allocation status nor the value can be changed. The callee may test allocated(u) and read the elements.

intent(out)

An actual argument that is allocated is deallocated on entry (9.7.3.2 and 15.5.2.7), so the callee always starts with an unallocated variable. Allocatable components of an intent(out) derived-type dummy argument are deallocated too.

intent(inout)

The callee may use the current value, reallocate it or deallocate it. An unallocated actual argument is passed unallocated.

none

Permitted, but the program loses the information about what the callee does. Always give an intent.

If the dummy argument has the target attribute and the actual argument does too, pointers associated with the actual argument remain associated with the dummy argument for the duration of the call (15.5.2.7). Without it, such pointers do not become associated with the dummy argument.

Allocatable function results

A function can return an allocatable variable, in which case the size need not be known by the caller. The caller assigns the result to an allocatable variable, which is allocated by the assignment (previous section), or uses it directly in an expression. The result is deallocated after the innermost executable construct that contains the reference. The result variable is a local variable of the function, but the usual rule that a local allocatable is deallocated on return does not apply to it.

Allocatable components

A component with the allocatable attribute is unallocated in a newly created object and is deallocated when the object is deallocated. Intrinsic assignment of a derived-type value copies the components: for an allocatable component the target is deallocated first, then reallocated with the shape of the source component, and the values are copied (10.2.1.3), a deep copy. A pointer component, by contrast, would be copied as a pointer and the two objects would share the target. That is the main practical argument for allocatable components.

The next example puts these three in one module: a function result (linspace), an intent(inout) allocatable dummy argument that keeps its contents when the shape does not change (ensure_field) and an allocatable component of field_t, filled with source= and deep-copied by assignment.

module heatflow_fields
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp, linspace, ensure_field, field_t, make_field

  integer, parameter :: dp = real64

  type :: field_t
    integer :: nx = 0, ny = 0
    real(dp), allocatable :: u(:, :)            ! allocatable component
  end type field_t

contains

  ! Allocatable function result: the caller does not need to know the size in advance.
  function linspace(a, b, n) result(x)
    real(dp), intent(in) :: a, b
    integer, intent(in) :: n
    real(dp), allocatable :: x(:)
    integer :: i
    allocate (x(n))
    do i = 1, n
      x(i) = a + (b - a)*real(i - 1, dp)/real(n - 1, dp)
    end do
  end function linspace

  ! Allocatable dummy, intent(inout): the callee may allocate, reallocate or deallocate the actual argument.
  subroutine ensure_field(u, nx, ny)
    real(dp), allocatable, intent(inout) :: u(:, :)
    integer, intent(in) :: nx, ny
    if (allocated(u)) then
      if (all(shape(u) == [nx, ny])) return      ! already the right shape: keep the data
      deallocate (u)
    end if
    allocate (u(nx, ny), source=0.0_dp)
  end subroutine ensure_field

  function make_field(nx, ny, value) result(f)
    integer, intent(in) :: nx, ny
    real(dp), intent(in) :: value
    type(field_t) :: f
    f%nx = nx
    f%ny = ny
    allocate (f%u(nx, ny), source=value)
  end function make_field

end module heatflow_fields

program dummies_results
  use heatflow_fields
  implicit none
  real(dp), allocatable :: u(:, :), x(:)
  type(field_t) :: f1, f2

  x = linspace(0.0_dp, 1.0_dp, 5)                  ! x is allocated by the assignment of the result
  print '(a, i0, a, 5f5.2)', 'linspace: size=', size(x), '  x=', x

  call ensure_field(u, 3, 2)                        ! u is unallocated here, which is allowed
  u(2, 2) = 5.0_dp
  call ensure_field(u, 3, 2)                        ! same shape: contents kept
  print '(a, 2i3, a, f4.1)', 'same shape: shape=', shape(u), '  u(2,2)=', u(2, 2)
  call ensure_field(u, 4, 4)                        ! different shape: reallocated, zero filled
  print '(a, 2i3, a, f4.1)', 'new shape:  shape=', shape(u), '  u(2,2)=', u(2, 2)

  f1 = make_field(3, 3, 1.0_dp)
  f2 = f1                                           ! intrinsic assignment copies the allocatable component (deep copy)
  f2%u(1, 1) = 99.0_dp
  print '(a, f5.1, a, f5.1)', 'deep copy: f1%u(1,1)=', f1%u(1, 1), '  f2%u(1,1)=', f2%u(1, 1)
end program dummies_results

Output (identical with Flang 23):

linspace: size=5  x= 0.00 0.25 0.50 0.75 1.00
same shape: shape=  3  2  u(2,2)= 5.0
new shape:  shape=  4  4  u(2,2)= 0.0
deep copy: f1%u(1,1)=  1.0  f2%u(1,1)= 99.0

x = linspace(...) allocated x, ensure_field was called with an unallocated u, and the assignment f2 = f1 copied the 3 by 3 array, so changing f2%u(1, 1) leaves f1%u(1, 1) alone. The same code with a pointer component would have printed 99.0 twice.

Unallocated actual arguments and optional dummy arguments

Apart from the inquiry functions such as allocated, an unallocated allocatable actual argument can be passed in only two ways: to an allocatable dummy argument, or to an optional dummy argument that is not allocatable or pointer. In the second case the dummy argument is simply not present (15.5.2.13): "has the ALLOCATABLE attribute and is not allocated" makes the corresponding dummy argument absent. This is a deliberately useful rule, because an optional argument can then be controlled by whether a variable has been allocated:

module heatflow_masks
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp, mean_temperature

  integer, parameter :: dp = real64

contains

  function mean_temperature(u, weights) result(m)
    real(dp), intent(in) :: u(:)
    real(dp), intent(in), optional :: weights(:)   ! not allocatable, but may be associated with an allocatable
    real(dp) :: m
    if (present(weights)) then
      m = sum(u*weights)/sum(weights)
    else
      m = sum(u)/real(size(u), dp)
    end if
  end function mean_temperature

end module heatflow_masks

program optional_alloc
  use heatflow_masks
  implicit none
  real(dp) :: u(4) = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp]
  real(dp), allocatable :: w(:)

  ! An unallocated allocatable actual argument is a legal way to say "argument absent"
  print '(a, f6.3)', 'w unallocated: mean=', mean_temperature(u, w)
  w = [4.0_dp, 3.0_dp, 2.0_dp, 1.0_dp]
  print '(a, f6.3)', 'w allocated:   mean=', mean_temperature(u, w)
end program optional_alloc

Output (identical with Flang 23):

w unallocated: mean= 2.500
w allocated:   mean= 2.000

Passing the unallocated array to an ordinary (non-optional) assumed-shape dummy argument is not allowed, and the compilers checked here do not diagnose it. This program is deliberately wrong:

! Deliberately wrong: an unallocated array passed to a non-allocatable dummy argument.
module m
  implicit none
contains
  subroutine total(v, s)
    real, intent(in) :: v(:)
    real, intent(out) :: s
    s = sum(v)
  end subroutine total
end module m

program unallocated_actual
  use m
  implicit none
  real, allocatable :: a(:)
  real :: s
  call total(a, s)
  print *, s
end program unallocated_actual

GNU Fortran 16.2 crashes inside the callee, even with -fcheck=all:

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

(backtrace omitted)
Segmentation fault

LLVM Flang 23 prints a meaningless 0. for the sum of an array that does not exist. Both are the usual form of undefined behavior: a crash if you are lucky.

Recursive allocatable components and polymorphic allocatables

Since Fortran 2008 a derived type may have an allocatable component of its own type, which turns allocatable components into a safe linked data structure with automatic cleanup: deallocating the head deallocates the whole chain, and assignment copies the whole chain. The example builds a probe list with move_alloc (it hands the old list to the new node’s next without copying), walks it by recursion, deep-copies it and allocates an unlimited polymorphic scalar.

module heatflow_probes
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp, probe_list, push, show

  integer, parameter :: dp = real64

  type :: probe_node                         ! recursive allocatable component (Fortran 2008)
    real(dp) :: temperature = 0.0_dp
    type(probe_node), allocatable :: next
  end type probe_node

  type :: probe_list
    type(probe_node), allocatable :: head
    integer :: count = 0
  end type probe_list

contains

  subroutine push(list, t)
    type(probe_list), intent(inout) :: list
    real(dp), intent(in) :: t
    type(probe_node), allocatable :: new_head
    allocate (new_head)
    new_head%temperature = t
    call move_alloc(list%head, new_head%next)   ! the old list becomes the tail
    call move_alloc(new_head, list%head)
    list%count = list%count + 1
  end subroutine push

  subroutine show(list)
    type(probe_list), intent(in) :: list
    write (*, '(a)', advance='no') 'list:'
    if (allocated(list%head)) call show_node(list%head)
    write (*, '(a)') ''
  end subroutine show

  recursive subroutine show_node(node)       ! walks the chain by recursion: no pointers needed
    type(probe_node), intent(in) :: node
    write (*, '(f6.1)', advance='no') node%temperature
    if (allocated(node%next)) call show_node(node%next)
  end subroutine show_node

end module heatflow_probes

program recursive_types
  use heatflow_probes
  implicit none
  type(probe_list) :: probes
  type(probe_list) :: copy
  class(*), allocatable :: anything
  integer :: k

  do k = 1, 4
    call push(probes, 10.0_dp*real(k, dp))
  end do
  call show(probes)

  copy = probes                                ! the standard requires every node to be copied
  copy%head%temperature = -1.0_dp
  print '(a, f6.1, a, f6.1)', 'original head=', probes%head%temperature, '  copy head=', copy%head%temperature

  allocate (anything, source=3.5_dp)           ! allocatable unlimited polymorphic scalar
  select type (anything)
  type is (real(dp))
    print '(a, f4.1)', 'dynamic type real(dp), value ', anything
  end select

end program recursive_types

Output (identical with Flang 23):

list:  40.0  30.0  20.0  10.0
original head=  40.0  copy head=  -1.0
dynamic type real(dp), value  3.5

Two caveats go with this design. First, deallocating the head deallocates the chain recursively. Building the same list with 1,000,000 nodes inside a subroutine (the module is the one above, the program only calls build (1000000), where build pushes the nodes and returns) gave count=1000000 and then Segmentation fault on return from build with GNU Fortran 16.2 (at -O0 and -O2), and LLVM Flang 23 crashed too (before it printed the count), with the default 8 MB stack. The cleanup of a long chain needs stack proportional to its length, so for long sequences use an array that grows with move_alloc. Second, traversing the chain needs either recursion, as here, or a pointer that is associated with each node in turn (the pointer is a view, owns nothing, and needs the target attribute on the head). Third, the standard requires the assignment copy = probes to copy every node (10.2.1.3), but this program changes only the head of the copy, so it does not test the deeper nodes. GNU Fortran 16.2 gets them wrong: from the third node on, the copy shares storage with the original, as Derived types shows. Test deep copies of recursive chains on every compiler you use.

Polymorphic allocatables are the third use. In heatflow, make_solver returns class(solver_t), allocatable :: s and allocates it with a type-spec chosen at run time:

allocate (jacobi_solver_t :: s)    ! or: allocate (ftcs_solver_t :: s)

The dynamic type is then jacobi_solver_t, and a select type construct or a type-bound call dispatches on it. allocate (s, source=other) clones a polymorphic object whose dynamic type is not known statically, and allocate (s, mold=other) makes an uninitialized object of the same dynamic type. See Object Oriented Programming and Type-Bound Procedures, Abstract Types and Finalization.

An allocatable coarray is allocated with a coshape, allocate (c(n)[*]), and every allocate and deallocate of a coarray, including the automatic deallocation at the end of a procedure, synchronizes all images of the current team (9.7.1.2, 9.7.3.2). See Coarrays, Images and Synchronization.

Automatic arrays, stack storage and the heap

A local array whose bounds are not constant is an automatic data object (8.3), for example real(dp) :: work(n) where n is a dummy argument or a module variable: its size is fixed on entry to the procedure and it exists until the procedure returns. It is declared, not allocated, so it cannot fail gracefully (there is no stat=), it cannot be reallocated, and it cannot have the save attribute (C814). An automatic array is the right tool for a small scratch array whose size follows an argument. Where it lives is not specified by the standard, and the compilers disagree:

program stack_versus_heap
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  integer, parameter :: n = 50000000          ! 50 million reals of 8 bytes = 400 MB
  character(len=16) :: mode

  call get_command_argument(1, mode)
  select case (trim(mode))
  case ('automatic')
    call with_automatic(n)
  case ('allocatable')
    call with_allocatable(n)
  case default
    print '(a)', 'usage: a.out automatic|allocatable'
  end select

contains

  subroutine with_automatic(m)                 ! automatic array: size is a dummy argument
    integer, intent(in) :: m
    real(dp) :: work(m)
    work = 1.0_dp
    print '(a, es10.3)', 'automatic:   sum = ', sum(work)
  end subroutine with_automatic

  subroutine with_allocatable(m)
    integer, intent(in) :: m
    real(dp), allocatable :: work(:)
    integer :: stat
    allocate (work(m), stat=stat)
    if (stat /= 0) then
      print '(a)', 'allocatable: allocation failed, the program can report it and go on'
      return
    end if
    work = 1.0_dp
    print '(a, es10.3)', 'allocatable: sum = ', sum(work)
  end subroutine with_allocatable

end program stack_versus_heap

The program allocates 400 MB of reals in a subroutine, either as an automatic array or as an allocatable array. It was compiled warning-free as usual. Run in a Linux container with the default stack limit of 8192 KiB (ulimit -s):

Build a.out automatic a.out allocatable

gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all

automatic: sum = 5.000E+07

allocatable: sum = 5.000E+07

gfortran -O2 -fstack-arrays

Segmentation fault

allocatable: sum = 5.000E+07

flang-23 (LLVM Flang 23)

Segmentation fault

allocatable: sum = 5.000E+07

By default GNU Fortran puts automatic arrays on the heap (a hidden malloc and free per call), so the first row works. -fstack-arrays moves "all arrays of unknown size and array temporaries onto stack memory" and is enabled by -Ofast (unless -fmax-stack-var-size is given). It is faster, because there is no allocator call, and it overflows the stack for a large array; the LLVM Flang 23 build crashed on the automatic array, so there it lives on the stack (its -fstack-arrays option is documented for array temporaries). The allocatable version never crashed, and its failure mode is the one the program handles: allocate with stat= returns an error code and the program can report it.

Rules of thumb that follow:

  • Use an automatic array for small scratch space and an allocatable array for anything whose size is user-controlled or could be large (grids, matrices, work arrays proportional to a problem size).

  • A crash with "Segmentation fault" on entry to a procedure that has a large automatic array or array temporary is a stack overflow until proven otherwise. Raising the limit (ulimit -s unlimited, the OMP_STACKSIZE variable for OpenMP threads) hides the problem; switching to allocatable removes it.

  • A fixed-size local array that is larger than a threshold is a different case in GNU Fortran. It is moved from the stack to static storage, silently making the procedure non-reentrant, with a warning:

module heatflow_scratch
  implicit none
contains
  subroutine smooth(u)
    real, intent(inout) :: u(:)
    real :: scratch(100000)          ! fixed-size local array of 400 000 bytes
    integer :: n
    n = min(size(u), size(scratch))
    scratch(1:n) = u(1:n)
    u(2:n - 1) = 0.5*(scratch(1:n - 2) + scratch(3:n))
  end subroutine smooth
end module heatflow_scratch

program big_local
  use heatflow_scratch
  implicit none
  real :: u(5) = [0.0, 1.0, 2.0, 3.0, 4.0]
  call smooth(u)
  print '(5f5.1)', u
end program big_local

GNU Fortran 16.2 compiled it with -Wall -Wextra and printed this warning (the program then ran correctly: the output is 0.0 1.0 2.0 3.0 4.0):

big_local.f90:6:13:

    6 |     real :: scratch(100000)          ! fixed-size local array of 400 000 bytes
      |             1~~~~~~~~~~~~~~
Warning: Array 'scratch' at (1) is larger than limit set by '-fmax-stack-var-size=', moved from stack to static storage. This makes the procedure unsafe when called recursively, or concurrently from multiple threads. Consider increasing the '-fmax-stack-var-size=' limit (or use '-frecursive', which implies unlimited '-fmax-stack-var-size') - or change the code to use an ALLOCATABLE array. If the variable is never accessed concurrently, this warning can be ignored, and the variable could also be declared with the SAVE attribute. [-Wsurprising]
  0.0  1.0  2.0  3.0  4.0

The warning is the compiler’s reminder that two threads, or a recursive call, would share scratch. The limit is set by -fmax-stack-var-size=n, default 65536 bytes (documented in the GNU Fortran manual); -frecursive removes the limit and uses the stack, and -fno-automatic forces static storage for every local. The cure that the warning itself suggests is the right one: "change the code to use an ALLOCATABLE array". LLVM Flang 23 compiled the same program without a warning.

Storage Declared as Lifetime Failure mode

Static

a module or main-program variable, a save local, a large fixed-size array in GNU Fortran

the whole program

load-time size limit (executable too large)

Automatic array

real(dp) :: work(n) in a procedure

until the procedure returns

stack overflow (or heap failure that terminates the program)

Allocatable

real(dp), allocatable :: work(:)

from allocate (or assignment) to deallocation or scope exit

stat /= 0, or error termination without stat=

Pointer target

allocate (p(n)) on a pointer

until deallocate through a pointer (never automatically)

leak, dangling pointer

Memory leaks

An allocatable variable cannot leak: whichever way control leaves its scope, the compiler-generated cleanup deallocates it (the rules of automatic deallocation, above), and an allocate of an allocated variable is an error, so a block cannot be lost by allocating over it. Pointers have none of these protections: allocate on an associated pointer "is not an error" (9.7.1.4), creates a new target and leaves the old one unreachable if no other pointer refers to it, and no pointer is ever deallocated automatically (a pointer target is freed only by a deallocate statement, 9.7.3.3).

The program below makes the contrast measurable. Both branches allocate 1000 reals three times in a loop; in the allocatable branch the loop frees each block as the rules require, and the variable allocated after the loop is freed by the automatic deallocation on return. In the pointer branch nothing is freed.

program leaks
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  character(len=16) :: which

  call get_command_argument(1, which)
  select case (trim(which))
  case ('allocatable')
    call with_allocatable()
  case ('pointer')
    call with_pointer()
  case default
    print '(a)', 'usage: a.out allocatable|pointer'
  end select

contains

  subroutine with_allocatable()
    real(dp), allocatable :: work(:)
    integer :: k
    do k = 1, 3
      allocate (work(1000))        ! allocating an allocated variable is an error, so every pass must free it
      work = real(k, dp)
      deallocate (work)
    end do
    allocate (work(1000))          ! never deallocated explicitly: freed automatically on return
    work = 0.0_dp
  end subroutine with_allocatable

  subroutine with_pointer()
    real(dp), pointer :: work(:)
    integer :: k
    do k = 1, 3
      allocate (work(1000))        ! the previous block is silently lost: pointers may be re-allocated
      work = real(k, dp)
    end do
  end subroutine with_pointer

end program leaks

The program was built with the strict options and run under Valgrind 3.24.0 in the container (valgrind --leak-check=full); the output below is filtered to the summary lines:

$ valgrind --leak-check=full ./a.out allocatable
==12==   in use at exit: 0 bytes in 0 blocks
==12==   total heap usage: 22 allocs, 22 frees, 37,575 bytes allocated
==12== All heap blocks were freed -- no leaks are possible

$ valgrind --leak-check=full ./a.out pointer
==14==   in use at exit: 24,000 bytes in 3 blocks
==14==   total heap usage: 21 allocs, 18 frees, 29,571 bytes allocated
==14== 24,000 bytes in 3 blocks are definitely lost in loss record 1 of 1
==14== LEAK SUMMARY:
==14==    definitely lost: 24,000 bytes in 3 blocks
==14==    indirectly lost: 0 bytes in 0 blocks

The allocatable branch frees every block; the pointer branch loses three blocks of 8000 bytes. The other 18 blocks that appear in both runs belong to the Fortran runtime and the standard library. Valgrind is described on Testing, Debugging and Profiling.

"Cannot leak" is not "never grows". Three patterns keep memory alive that nobody needs:

  • an allocatable with save, or declared in a module or the main program, lives until the program ends, and a work array stored in it is never freed even though it is no longer used;

  • an allocatable component of a long-lived object, such as a log that is only appended to (the history of the heatflow_stats module on Modules and Submodules) grows with every call;

  • a pointer to an allocatable (which needs the target attribute) can dangle when the allocatable is deallocated, reallocated by assignment or moved by move_alloc without target on the new owner (9.7.3.2 and 16.9.147).

Allocatable or pointer

Both attributes give you run-time sized objects. The guidance in the fortran-lang best practices and in the usual Fortran literature is the same: use allocatable unless you need what only a pointer can do.

Need Use Reason

A work array, a grid, a table, a result of unknown size

allocatable

automatic deallocation, no aliasing, contiguous by definition, and the optimizer may assume nothing else refers to it

A growable sequence

allocatable with move_alloc

the amortized pattern above

A component that must be copied with its owner

allocatable component

intrinsic assignment makes a deep copy; with a pointer component two objects share one target

Return an array of unknown size from a function

allocatable result

no pointer to free, no leak when the caller ignores the result

An optional piece of data

allocatable scalar or array

allocated is the test, and an unallocated actual argument turns an optional dummy argument off

A view of part of another array (a section, a row, a diagonal), or an alias with a different shape

pointer (to a target)

a pointer is the only way to name a section as a variable; see Pointers and Targets

A linked structure in which several objects refer to one node, a graph, a tree with parent pointers

pointer

shared ownership does not fit allocatable

A procedure pointer, or a callback stored in a type

procedure pointer

allocatable procedures do not exist

Interoperating with C, where C owns the memory

pointer (c_f_pointer) or assumed-shape dummy arguments

see Interoperability with C

Most of the time an allocatable array is also faster: a pointer array may be non-contiguous and aliased, so the compiler must generate conservative code (or a contiguity check) where it can assume the opposite for an allocatable.

Runtime errors and diagnostics

The common failures are shown by one program that runs one deliberately wrong case chosen from the command line:

! Deliberately wrong: each case triggers one runtime error; the case is chosen on the command line.
program allocation_runtime_errors
  implicit none
  real, allocatable :: a(:)
  character(len=16) :: which

  call get_command_argument(1, which)
  select case (trim(which))
  case ('reference')            ! reading an unallocated array
    print *, a(1)
  case ('allocate-twice')       ! allocate without stat= on an allocated array
    allocate (a(3))
    allocate (a(4))
  case ('deallocate-twice')     ! deallocate without stat= on an unallocated array
    allocate (a(3))
    deallocate (a)
    deallocate (a)
  case ('bounds')               ! subscript beyond the allocated extent
    allocate (a(3))
    a(4) = 1.0
  case ('whole')                ! whole-array use of an unallocated array
    print *, sum(a)
  case default
    print '(a)', 'usage: a.out reference|allocate-twice|deallocate-twice|bounds|whole'
  end select
end program allocation_runtime_errors

Built with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2); for each argument the first two lines of output are:

$ ./a.out reference
At line 10 of file runtime_errors.f90
Fortran runtime error: Index '1' of dimension 1 of array 'a' below lower bound of 281474053255328
$ ./a.out allocate-twice
At line 13 of file runtime_errors.f90
Fortran runtime error: Attempting to allocate already allocated variable 'a'
$ ./a.out deallocate-twice
At line 17 of file runtime_errors.f90
Fortran runtime error: Attempt to DEALLOCATE unallocated 'a'
$ ./a.out bounds
At line 20 of file runtime_errors.f90
Fortran runtime error: Index '4' of dimension 1 of array 'a' above upper bound of 3
$ ./a.out whole
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

With LLVM Flang 23 and no extra options:

$ ./a.out reference
Segmentation fault
$ ./a.out allocate-twice
(no output, exit status 0)
$ ./a.out deallocate-twice
(no output, exit status 0)
$ ./a.out bounds
(no output, exit status 0)
$ ./a.out whole
 0.

The two cases that reference an unallocated array are undefined behavior (9.7.1.3 and 8.5.8.4): the first message shows a garbage lower bound read from the descriptor, and whole printed 0.00000000 on one run and crashed on another. GNU Fortran also warned about both at compile time ('a.dim[0].lbound' may be used uninitialized [-Wmaybe-uninitialized]), which is the cheapest diagnostic available. Allocating an allocated variable and deallocating an unallocated one are error conditions that the standard requires to terminate the program when there is no stat=; Flang 23 as tested here ran on silently, so do not rely on it to find those bugs. The out-of-bounds subscript is only caught when bounds checking is on (-fcheck=bounds, part of -fcheck=all).

GNU Fortran option What it checks or does for allocatables

-fcheck=bounds

subscripts against the actual allocated bounds of a deferred-shape array, and string lengths in array constructors

-fcheck=pointer

run-time checks for pointers and allocatables

-fcheck=mem

memory allocation, such as the hidden allocations for automatic arrays and temporaries; explicit allocate is always checked

-fcheck=array-temps

warns at run time when a temporary array had to be created for an actual argument

-fcheck=all

all of the above and more

-Wmaybe-uninitialized (with optimization)

unallocated arrays that are referenced, as in the compile-time warnings above

-Warray-temporaries

compile-time report of array temporaries

-Wrealloc-lhs, -Wrealloc-lhs-all

assignments that may reallocate

-fmax-stack-var-size=n, -fstack-arrays, -fno-automatic, -frecursive

where local arrays live

Performance

  • Allocate once, reuse. allocate and deallocate go to the system allocator and are slow compared with arithmetic. A time-stepping loop such as the one in heatflow allocates its work arrays before the first step; the Jacobi and FTCS solvers do it on first use (if (.not. allocated(self%work)) self%work = self%grid%u) and keep the array for all later steps.

  • Do not grow one element at a time. x = [x, v] is correct and compact, and it copies the whole array each time. Use capacity doubling with move_alloc, as above, or compute the final size first.

  • Beware of allocatable function results in loops (previous sections). Each reference allocates and frees. Prefer a subroutine with an intent(out) argument and an array allocated once, and avoid allocate inside do concurrent and OpenMP regions unless each iteration really needs private storage.

  • x = y may reallocate. In hot loops write x(:) = y or use the compiler warnings above to find assignments that contain a hidden shape check and allocation.

  • source= initialises. allocate (a(n), source=0.0_dp) writes every element, which is what you want for an accumulator and a waste for an array that is overwritten next. Without it the contents are undefined.

  • Allocatable beats pointer for the optimizer (guidance above).

  • Large allocations are typically committed lazily on Linux: allocate of a large array may succeed and the program may be killed when the pages are touched. stat= cannot catch that.

More on memory layout, contiguity and temporaries is on Performance and Memory Layout.

Memory that C allocated and memory that Fortran allocated must be returned by the side that allocated it: never pass the storage of a Fortran allocatable array to C free, and never deallocate a pointer that was set by c_f_pointer from a C malloc. The C descriptor functions CFI_allocate and CFI_deallocate exist for allocatable arrays shared with C; see C Descriptors and ISO_Fortran_binding.

Coming from another language

In C, C++, Python or MATLAB In Fortran Difference

double *p = malloc (n*sizeof *p); ... free (p);

real(dp), allocatable :: p(:), allocate (p(n))

No free: the scope does it. The bounds are part of the object, so there is no separate length variable.

realloc

move_alloc with a new allocation, or x = [x, v]

There is no in-place extension; every growth copies.

std::vector<double>

allocatable array with a count and move_alloc growth

No push_back; the pattern above is the idiom, because the language itself has no container type.

std::unique_ptr

allocatable

Sole ownership, automatic release and a deep copy on assignment are the default behavior of an allocatable.

std::shared_ptr, a raw pointer

pointer

Aliasing and manual release.

NumPy a = np.zeros (n); a = np.append (a, v)

allocate (a(n), source=0.0_dp); a = [a, v]

Same cost as np.append: a new array on every call.

MATLAB x(end+1) = v

x = [x, v]

Same: the array is copied; MATLAB hides it.

Java and Python object references

class(t), allocatable, or a pointer to share

Assignment of an allocatable copies the object, including its allocatable components; two names for one object need a pointer.

See Fortran for Programmers of Other Languages for the full comparison.

Compiler support and diagnostics

The following was verified on 2026-10-18 with the examples on this page; the "not verified" family names mean that no compiler of that family was available.

Feature GNU Fortran 16.2 LLVM Flang 23

allocate and deallocate with stat=, fixed-length errmsg=, source=, mold= and a type-spec

Accepted; all examples built warning-free with -std=f2023 -Wall -Wextra -pedantic -fcheck=all.

Accepted, with different stat codes and message texts.

Reallocation on assignment, including bounds from lbound (expr) and deferred-length character

Yes, on by default (-frealloc-lhs); -Wrealloc-lhs and -Wrealloc-lhs-all report it.

Yes.

Error termination for allocate of an allocated variable and deallocate of an unallocated one, without stat=

Terminates with a message.

In the test for this page the program ran on with exit status 0 and no message.

errmsg= with a deferred-length allocatable character (F2023)

Compiles with a warning; crashes with SIGSEGV when an error occurs.

Compiles; the message is not stored and the variable stays unallocated.

Array expressions as allocate bounds (F2023)

Rejected: "Rank mismatch in array reference" and "Array index must be scalar".

Rejected: "Must be a scalar value, but is a rank-1 array".

rank clause (F2023)

Rejected ("Invalid character in name").

Accepted.

move_alloc with stat= and errmsg= keywords

Accepted; the positional form call move_alloc (a, b, stat, msg) caused an internal compiler error.

Accepted in both forms.

Recursive allocatable component (F2008), class(*), allocatable, final subroutines run on automatic deallocation

Yes.

Yes.

Run-time checking of allocatable references (-fcheck=bounds and -fcheck=pointer)

Yes; see the table of options above.

Not available in the version tested (-fbounds-check is rejected).

Not verified here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers. See History, Standards and Compilers for the per-compiler support table of the other Fortran 2023 features, and Compilers and Compiler Options for the options.

Standard-version provenance

Feature Standard Notes

allocatable arrays (local and module variables), allocate, deallocate, allocated, stat=, zero-sized allocation

Fortran 90

The first form of dynamic memory in standard Fortran; pointer arrays arrived at the same time.

Automatic deallocation of local allocatable arrays on return

Fortran 95

In Fortran 90 their status after return was not defined, which made them unreliable in procedures.

Allocatable dummy arguments, function results and components (the technical report ISO/IEC TR 15581)

Fortran 2003

Incorporated into the standard in Fortran 2003, with allocatable scalars.

Reallocation on assignment, move_alloc, source=, errmsg=, deferred-length character, polymorphic allocatables

Fortran 2003

-std=f95 in GNU Fortran turns reallocation off.

mold= in allocate, recursive allocatable components, allocatable coarrays

Fortran 2008

Optional stat= and errmsg= in move_alloc, simple move_alloc (when from is not a coarray)

Fortran 2018 or 2023 (the Fortran 2023 text has both)

Not in Fortran 2008.

Arrays as bounds in allocate, the rank clause, deferred-length allocatable errmsg= and iomsg=

Fortran 2023

None of the three works in GNU Fortran 16.2, and only the rank clause works in LLVM Flang 23.

Allocation of an unallocated coarray or coindexed object by assignment

Removed in Fortran 2023

It was permitted in Fortran 2008 (4.3.4 of the Fortran 2023 text) for scalar, nonpolymorphic coarrays without deferred type parameters.

Fortran 2028 (draft J3/26-007r1)

Upcoming

Not covered by this page; see What’s New and Fortran 2028 Preview.

Where the books are outdated

Modern Fortran Explained (Metcalf, Reid, Cohen and Bader, 3rd edition, Fortran 2023) covers allocatable variables, dummy arguments, results and components, allocate and deallocate, reallocation on assignment and move_alloc in its chapter 6, with the Fortran 2023 additions near the end. As a reference to the language it remains accurate. The points where a reader has to look beyond it are:

  • Compiler status. The book describes the Fortran 2023 features as part of the language and does not say which compilers implement them. As the table above shows, the array bounds in allocate and the allocation of a deferred-length errmsg do not work in GNU Fortran 16.2 or LLVM Flang 23, and the rank clause works only in Flang. Portable code writes the scalar bounds and a fixed-length message variable.

  • Compiler options and diagnostics. The book says nothing about -frealloc-lhs, -Wrealloc-lhs, -fcheck=bounds, -fstack-arrays or -fmax-stack-var-size, about the run-time messages of allocation errors, or about the fact that "Segmentation fault" on entry to a procedure with a large automatic array is a stack overflow. They are covered above.

  • Stack and heap. The book does not discuss where automatic arrays live. As the experiment above shows, the answer depends on the compiler and on its options.

  • Diagnosing leaks. The book shows that allocatables do not leak but gives no tool to find the leaks that pointers still allow; Valgrind and -fcheck=pointer are described above.

  • Appendix A. The book’s list of deprecated features is the authors' recommendation. None of what this page covers (allocate, pointer, reallocation on assignment) is obsolescent in the standard.

The fortran-lang best-practices page on allocatable arrays is current in its advice, but its resize snippet declares neither its work array tmp nor consistently spells initial_size, so it does not compile as printed; use the pattern above.

Common mistakes at a glance

Mistake Symptom Fix

allocate of an already allocated variable

"Attempting to allocate already allocated variable", or error termination

if (.not. allocated (x)) allocate (x(n)), or deallocate first, or assign x = ....

deallocate of an unallocated variable

"Attempt to DEALLOCATE unallocated", or error termination

Test with allocated, or rely on automatic deallocation.

Reading an unallocated array

Garbage, "below lower bound of" a huge number, or a crash

Allocate before use; build with -Wall -O2 (maybe-uninitialized) and -fcheck=all.

Passing an unallocated array to a non-allocatable, non-optional dummy argument

Crash inside the callee

Allocate first, or make the dummy argument optional, or allocatable.

x(:) = y with a different size

"Array bound mismatch" (or silent overrun without -fcheck=bounds)

Write x = y so the array is reallocated.

Assuming lower bound 1 after x = y

Off-by-one reads

Remember that x takes lbound (y); use x = y(:) or pass to an assumed-shape dummy argument.

label = name for a deferred-length character

Trailing blanks in the new value

label = trim (name).

x = [x, v] in a loop

Quadratic time

Capacity doubling with move_alloc.

Allocatable function result called in a loop

Repeated allocation and deallocation

Pass the output array as an intent(out) argument allocated once.

Large automatic array

"Segmentation fault" on entry to a procedure

Use an allocatable array, or raise the stack limit as a stop-gap.

Large fixed-size local array in GNU Fortran

-Wsurprising: moved from stack to static storage

Use an allocatable array.

Pointer to an allocatable without target, or move_alloc to a variable without target

Undefined pointer

Add target or avoid the pointer.

Deallocating an object while one of its parts is a dummy argument

Not allowed, no diagnostic

Deallocate from the owner after the call.

Relying on a particular stat value or message text

Works on one compiler only

Test stat /= 0.

errmsg= as a deferred-length character

Crash on GNU Fortran 16.2

Use a fixed-length variable.

Counting on a final subroutine of a main-program variable

It does not run at the end of the program

Put the variable in a block construct.