Pointers and Targets

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 with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and the examples were also built and run with LLVM Flang 23; the output shown is real output. Examples that are deliberately wrong, or that expose a difference between the compilers, are labelled as such and show the real diagnostics. The rules are in four places in the standard: the pointer attribute is 8.5.14 and the target attribute 8.5.18, the allocate, nullify and deallocate statements are 9.7, pointer assignment is 10.2.2, and pointer association (status, the events that change it and the contexts in which it may change) is 19.5.2 and 19.6.8. The intrinsic functions are associated (16.9.20), null (16.9.155) and is_contiguous (16.9.115), and Annex C.6 has the standard’s own pointer examples.

A pointer in Fortran is a variable that can be associated, during execution, with other variables, its targets. It is not an address that can be printed, added to or cast: it is a strongly typed alias that carries the type, kind, rank and, for arrays, the shape and bounds of whatever it currently refers to. Using the pointer in an expression is using its target, so there is no dereference operator. In modern code pointers are the exception and allocatable is the rule, as the comparison in Pointer or allocatable explains. They remain the right tool for four jobs: aliasing part of an existing array (a row, a window, a reshaped view), swapping two large buffers without copying them, building linked and tree structures, and returning a reference that outlives a call. The examples use the temperature grid of the running example, heatflow, as the thing pointed at: grid_t keeps its temperatures in an allocatable array, the pointer examples take windows into it, and the double-buffering example is the pattern a Jacobi sweep uses. Allocatable arrays are covered in Allocatable Arrays and Dynamic Memory, derived types and their pointer components in Derived Types, pointers to procedures in Interfaces, Generics and Operator Overloading and Procedures, Functions and Subroutines, and C pointers (c_ptr, c_f_pointer) in Interoperability with C.

The pointer and target attributes

A data pointer is declared with the pointer attribute (8.5.14). A data object can be the target of a pointer only if it has the target attribute (8.5.18) or is itself a pointer.

real(dp), pointer :: p                   ! scalar pointer
real(dp), pointer :: row(:), field(:, :) ! array pointers: the rank is fixed, the extents come from the target
real(dp), target  :: temperature         ! may be pointed at
type(grid_t), target :: g                ! every nonpointer subobject, such as g%u(2, 3), is a target too
real(dp), pointer, contiguous :: v(:)    ! may only be associated with contiguous targets

The rules that matter in practice:

Rule Detail Clause

A pointer has a rank, not a shape

an array pointer is declared with a deferred-shape specification, (:) or (:, :); its bounds and extents are those of the current target

8.5.8

pointer excludes allocatable, target and coarrays

an entity cannot be pointer and allocatable, pointer and target, or a pointer coarray (C854, C866)

8.5.14

target is a promise to the compiler

an object without the target attribute shall not have a pointer associated with it (C1028 enforces this in pointer assignment), and with the attribute the compiler must assume that pointers may alias the object

8.5.18

Subobjects inherit target

if an object has target, all of its nonpointer subobjects have it too, so g%u(2, 3) is a valid target when g is

8.5.18

Allocated pointer targets are targets

the object created by allocate(p) implicitly has the target attribute

9.7.1.4

A pointer is not referenced until it is associated

a data pointer shall not be referenced unless it is associated with a defined target, nor defined unless it is associated with a definable target

8.5.14

A named constant cannot be a target

a parameter is not a variable, and the target attribute is only for variables (C865)

8.5.18

A target can be an ordinary, module, local or allocatable variable, or a dummy argument. A pointer can point at a section of a target, such as g%u(2:nx-1, 2:ny-1), at an element, at a component such as g%nx, or at a character substring. A procedure pointer (8.5.14, procedure(iface), pointer :: f) is a different kind of entity that shares the attribute and the => syntax; it is covered with the procedures.

Pointer assignment, dereferencing and associated

The => statement associates a pointer with a target (R1034, 10.2.2). The = statement assigns values: when the left side is a pointer it assigns to the target of that pointer (C.6.3). The two look alike and do different things: p => t makes p an alias of t, whereas p = t copies the value of t into whatever p is associated with, and is an error (usually a crash) when p is not associated. In an expression a pointer simply delivers the value of its target (C.6.2).

associated(p) is true when p is associated with some target. associated(p, t) is true when p is associated with t in particular, which for an array means the same elements in the same order, so associated(row, field(2, :)) is true after row => field(2, :). The two-argument form with a pointer as the second argument, associated(q, p), is the way to ask whether two pointers alias each other. The first argument shall not have undefined association status (16.9.20), a point that matters in Association status.

program pointer_basics
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp), target :: temperature = 20.0_dp      ! may be pointed at
  real(dp), target :: other = 99.0_dp
  real(dp), pointer :: p => null()               ! disassociated from the start
  real(dp), pointer :: q => null()

  print '(a, l1)', 'p associated at start:        ', associated(p)

  p => temperature                                ! pointer assignment
  print '(a, l1, a, f6.1)', 'after p => temperature:      ', associated(p), '  p = ', p

  p = 25.0_dp                                     ! ordinary assignment through the pointer
  print '(a, f6.1)', 'temperature after p = 25.0:   ', temperature

  q => p                                          ! q gets the same target, not a copy
  print '(a, l1, a, l1)', 'associated(q, p)=', associated(q, p), '  associated(q, temperature)=', &
    associated(q, temperature)
  print '(a, l1)', 'associated(q, other)=      ', associated(q, other)

  p => other                                      ! re-point p; q still sees temperature
  print '(a, f6.1, a, f6.1)', 'p = ', p, '  q = ', q

  nullify (p)                                     ! break the association
  print '(a, l1)', 'after nullify(p), associated: ', associated(p)
  p => null()                                     ! equivalent way to disassociate
  print '(a, l1)', 'after p => null(), associated:', associated(p)
end program pointer_basics

Output:

p associated at start:        F
after p => temperature:      T  p =   20.0
temperature after p = 25.0:     25.0
associated(q, p)=T  associated(q, temperature)=T
associated(q, other)=      F
p =   99.0  q =   25.0
after nullify(p), associated: F
after p => null(), associated:F

The same output was produced by LLVM Flang 23. Three details of that program are worth stating. A declaration such as real(dp), pointer :: p => null() gives the pointer a defined, disassociated status from the first statement on, and implies save (8.4). After q => p, q is associated with temperature, the target, not with p: re-pointing p to other leaves q alone. And nullify (p) and p => null() both disassociate; they differ only in that nullify takes a list and needs no type information.

A pointer assignment to something that is not a target is rejected at compile time when the compiler can see it. This program is deliberately wrong:

program no_target
  implicit none
  real :: x = 1.0                 ! no target attribute
  real, pointer :: p
  p => x
  print *, p
end program no_target

GNU Fortran 16.2 reports:

bad_target.f90:5:7:

    5 |   p => x
      |       1
Error: Pointer assignment target is neither TARGET nor POINTER at (1)

and LLVM Flang 23:

error: Semantic errors in bad_target.f90
bad_target.f90:5:3: error: In assignment to object pointer 'p', the target 'x' is not an object with POINTER or TARGET attributes
    p => x
    ^^^^^^
bad_target.f90:3:11: Declaration of 'x'
    real :: x = 1.0                 ! no target attribute
            ^

The restrictions on what may appear on either side of => are the constraints C1016 to C1029 of 10.2.2.2: the pointer and target must be type compatible with equal kind parameters, the ranks must agree (unless bounds remapping is used), the target cannot be an array section with a vector subscript, and neither side can be coindexed. A pointer assignment whose target is a function reference is allowed only when the function returns a data pointer.

Association status

At every moment a data pointer has one of three association statuses (19.5.2.2): associated (it has a target), disassociated (it has none and is known to have none) and undefined (nobody can say, and the pointer cannot be used again until its status is re-established). A pointer that is not initialized, explicitly or by default, starts as undefined. Only associated or disassociated pointers may be passed to associated (16.9.20) or, for an array pointer, to size and the other inquiries that need a target (16.9.194); an undefined pointer may only be given a new status. The diagram shows the three statuses and the events that move a pointer between them.

stateDiagram-v2 direction LR [*] --> Undefined : declared without initialization [*] --> Disassociated : declared with null init [*] --> Associated : declared with a target Undefined --> Associated : allocate or pointer assignment to a target Undefined --> Disassociated : nullify or pointer assignment of null Disassociated --> Associated : allocate or pointer assignment to a target Associated --> Associated : pointer assignment to another target Associated --> Disassociated : nullify or deallocate through the pointer or pointer assignment of null Associated --> Undefined : target deallocated through another name or target ceases to exist Disassociated --> Undefined : assigned from an undefined pointer or passed to an intent out pointer dummy Associated --> Undefined : assigned from an undefined pointer

The events themselves are listed in the standard; this table groups the ones a programmer meets:

Result Events Clause

becomes associated

allocate (p); p => target with an associated pointer or a target; a pointer dummy argument whose actual argument is not a pointer (15.5.2.8); default initialization to a target; allocate with source= of an object whose component is associated

19.5.2.3

becomes disassociated

nullify (p); deallocate (p); p => null() or assignment from a disassociated pointer; default initialization with null() when an object comes into existence

19.5.2.4

becomes undefined

assignment from an undefined pointer; the target is deallocated through another name; the target is a local variable of a procedure that returns, or of a block that ends, and the pointer is not saved or accessed by host association; a do concurrent iteration ends with the pointer associated with a local variable; an object with pointer components and no default initialization is allocated or passed to an intent(out) dummy; a pointer is the actual argument for an intent(out) pointer dummy

19.5.2.5

Two consequences deserve emphasis. First, deallocate (p) disassociates p and every other pointer associated with the same target becomes undefined, not disassociated (9.7.3.3): the standard gives the program no way to discover that the other pointers have been invalidated. Second, undefined is not the same as "false from associated`" and a compiler is not required to detect it. A pointer variable without an initializer is undefined, so even `associated (p) is not allowed on it (16.9.20). This program is deliberately wrong; with -Wall GNU Fortran warns, and whatever it prints is luck:

program undefined_status
  implicit none
  real, pointer :: p     ! no initialization: association status is undefined (19.5.2.2)
  print *, associated(p) ! not allowed: argument status shall not be undefined
end program undefined_status

GNU Fortran 16.2 warns:

undefined_status.f90:4:25:

    4 |   print *, associated(p) ! not allowed: argument status shall not be undefined
      |                         ^
Warning: 'p' may be used uninitialized [-Wmaybe-uninitialized]
undefined_status.f90:3:20:

    3 |   real, pointer :: p     ! no initialization: association status is undefined (19.5.2.2)
      |                    ^
note: 'p' was declared here
 T

and the program printed T with GNU Fortran 16.2 and F with LLVM Flang 23, from the same uninitialized pointer. The rule is simple: give every pointer variable and every pointer component an initializer (=> null()) so that it never is undefined, as the next section does.

Initialization: null() and default initialization

The intrinsic null() (16.9.155) yields a disassociated pointer. In a declaration it initializes the pointer, in a derived type it is the default initialization of a pointer component (7.5.4.6), and in a pointer assignment, an actual argument or a structure constructor it takes its type from the context. null(mold) gives a typed result where the context does not, for example to select a specific procedure in a generic call.

Fortran 2008 added the second form, => target, an initial data target (R805, R744): the pointer is associated at the start of the program with a target that has the save attribute and whose subscripts are constant expressions (C770, C812). A component can be initialized the same way, which gives every object of the type a pointer to the same saved target.

module probe_data
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp), target, save :: ambient = 15.0_dp          ! saved: usable as initial-data-target
  type :: probe_t
    real(dp), pointer :: reading => null()              ! default initialization
    real(dp), pointer :: window(:) => null()
    character(len=8) :: name = 'unnamed'
  end type probe_t

  real(dp), pointer :: sensor => ambient                ! initial-data-target (Fortran 2008)
  real(dp), pointer :: nothing => null()
end module probe_data

program init_demo
  use probe_data
  implicit none
  type(probe_t) :: a, b

  print '(a, l1, a, f5.1)', 'sensor: associated=', associated(sensor), '  value=', sensor
  print '(a, l1)', 'nothing: associated=', associated(nothing)
  print '(a, l1, l1, 1x, a)', 'a%reading, a%window associated: ', associated(a%reading), &
    associated(a%window), trim(a%name)

  a%reading => ambient
  b = a                                                 ! intrinsic assignment: pointer components are copied as pointers
  print '(a, l1, a, l1)', 'after b = a: associated(b%reading, ambient)=', associated(b%reading, ambient), &
    '  same target as a: ', associated(a%reading, b%reading)
  b%reading = 16.0_dp
  print '(a, f5.1)', 'ambient (changed through b%reading): ', ambient
end program init_demo

Output (identical with GNU Fortran 16.2 and LLVM Flang 23):

sensor: associated=T  value= 15.0
nothing: associated=F
a%reading, a%window associated: FF unnamed
after b = a: associated(b%reading, ambient)=T  same target as a: T
ambient (changed through b%reading):  16.0

The last two lines of the output show the rule for pointer components under intrinsic assignment (10.2.1.3): b = a copies the pointer, so b%reading and a%reading alias one target, and a write through b%reading changes ambient. Allocatable components behave the other way and are deep-copied. This is the main reason a derived type with a pointer component needs a defined assignment, or care, whenever it is copied.

An initial data target of array pointer type is where the two compilers differ. The standard allows real, pointer :: whole(:) => history for a saved history, and GNU Fortran 16.2 accepts the statement without a warning but does not associate the pointer. This is a limitation of the installed compiler, shown here with its real behaviour:

program init_array
  implicit none
  real, target, save :: history(4) = [1.0, 2.0, 3.0, 4.0]
  real, pointer :: whole(:) => history
  real, pointer :: part(:) => history(3:4)
  real, pointer, save :: saved_whole(:) => history
  print '(a, 3l2)', 'associated (whole, part, saved_whole): ', associated(whole), associated(part), associated(saved_whole)
end program init_array

GNU Fortran 16.2 prints F F F, and LLVM Flang 23 prints T T T. A reference that needs the association is detected at run time by -fcheck=all:

program init_array2
  implicit none
  real, target, save :: history(4) = [1.0, 2.0, 3.0, 4.0]
  real, pointer :: whole(:) => history
  print '(a, l1, a, i0)', 'associated: ', associated(whole), '  size: ', size(whole)
  print '(a, l1)', 'associated(whole, history): ', associated(whole, history)
end program init_array2
At line 5 of file init_array2.f90
Fortran runtime error: Pointer argument 'whole' is not associated

(the backtrace lines were removed). LLVM Flang 23 prints associated: T size: 4 and associated(whole, history): T. Until the compiler is fixed, associate array pointers with an ordinary => statement in the executable part (a block or an initialization routine) and keep initial data targets for scalars, as sensor does above. Scalar initial data targets and null() are unaffected.

Array pointers: sections, bounds and remapping

An array pointer has a rank and nothing else until it is associated. Its extents are the extents of the target, and its lower bounds are the lower bounds of the target when the target is a whole array or another pointer, and 1 when the target is a section or an expression (10.2.2.3). The programmer can override the lower bounds, and for a rank-one or simply contiguous target, rank and extents:

Form (R1034) Effect

p => a or p => a(2:5, :)

extents from the target; lower bound from lbound of a whole array, else 1

p(0:) => a (bounds-spec)

same extents, the stated lower bounds

p(1:3, 1:4) => flat (bounds-remapping)

any rank and extents whose total number of elements does not exceed that of the target; the target shall be rank one or simply contiguous (9.5.4); elements are taken in array element order

p(lb:ub) => a with rank-one arrays

Fortran 2023: the lower and upper bounds are rank-one integer arrays of constant size equal to the rank (C1020, C1021)

The next program shows a window into a grid (the interior of a heatflow temperature array, where the sweeps operate), strided and unstrided rows, a lower bound override, a vector viewed as a matrix with two different sets of bounds, and the main diagonal of a matrix reached by viewing the matrix as a vector first.

program array_pointers
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  integer, parameter :: nx = 5, ny = 4
  real(dp), target :: u(nx, ny)
  real(dp), target :: flat(12)
  real(dp), target :: m(4, 4)
  real(dp), pointer :: interior(:, :), row(:), col(:), shifted(:), grid3x4(:, :), diag(:), as_vector(:)
  integer :: i, j

  u = reshape([(real(i, dp), i = 1, nx * ny)], [nx, ny])

  interior => u(2:nx - 1, 2:ny - 1)         ! pointer to a section: bounds restart at 1
  print '(a, 2i3, a, 2i3, a, 2i3)', 'interior shape', shape(interior), '  lbound', lbound(interior), &
    '  ubound', ubound(interior)
  interior = 0.0_dp                         ! writes to the target section
  print '(a, 5f5.0)', 'row 3 of u after interior = 0: ', u(3, :)

  row => u(1, :)                            ! strided: not contiguous
  col => u(:, 1)                            ! contiguous
  print '(a, l1, a, l1)', 'is_contiguous(row)=', is_contiguous(row), '  is_contiguous(col)=', is_contiguous(col)

  shifted(0:) => u(:, 2)                    ! bounds-spec: choose the lower bound
  print '(a, 2i3)', 'shifted lbound, ubound', lbound(shifted), ubound(shifted)

  m = reshape([(real(i, dp), i = 1, 16)], [4, 4])
  as_vector(1:16) => m                      ! rank 2 target seen as rank 1 (target is simply contiguous)
  diag => as_vector(1::5)                   ! every 5th element: the main diagonal of m
  diag = -diag
  print '(a, 4f6.0)', 'diagonal of m after diag = -diag: ', (m(i, i), i = 1, 4)

  flat = [(real(i, dp), i = 1, 12)]
  grid3x4(1:3, 1:4) => flat                 ! bounds remapping: rank 1 target seen as 3x4
  print '(a, 2i3, a, 4f5.0)', 'grid3x4 shape', shape(grid3x4), '   row 2: ', grid3x4(2, :)
  grid3x4(0:2, 10:13) => flat               ! same elements, other bounds
  print '(a, 2i3, 2i4, a, f5.0)', 'lbound', lbound(grid3x4), ubound(grid3x4), '   grid3x4(2, 13)=', grid3x4(2, 13)

  j = 0
  do i = lbound(grid3x4, 1), ubound(grid3x4, 1)
    j = j + count(grid3x4(i, :) > 6.0_dp)
  end do
  print '(a, i0)', 'elements > 6 seen through the remapped pointer: ', j
end program array_pointers

Output (identical with GNU Fortran 16.2 and LLVM Flang 23):

interior shape  3  2  lbound  1  1  ubound  3  2
row 3 of u after interior = 0:    3.   0.   0.  18.
is_contiguous(row)=F  is_contiguous(col)=T
shifted lbound, ubound  0  4
diagonal of m after diag = -diag:    -1.   -6.  -11.  -16.
grid3x4 shape  3  4   row 2:    2.   5.   8.  11.
lbound  0 10   2  13   grid3x4(2, 13)=  12.
elements > 6 seen through the remapped pointer: 6

Reading the output: interior has shape 3 by 2 and lower bounds 1 because it points at a section; row (u(1, :)) is not contiguous and col (u(:, 1)) is, a distinction is_contiguous reports and that matters when the pointer is passed to C or to code that assumes contiguity (see Performance and Memory Layout); a lower-bound override changes lbound and ubound only; and a bounds-remapped pointer indexes the same storage with new bounds, so grid3x4(2, 13) is the twelfth element of flat seen through grid3x4(0:2, 10:13). Because the rank-two m is a whole array, as_vector(1:16) => m is a legal remapping, and a pointer to every fifth element of that vector is the diagonal.

Fortran 2023 bounds from arrays. Fortran 2023 allows the bounds in a pointer assignment, like those in allocate, to be given as rank-one arrays: g(lb:ub) => flat with lb and ub of constant size equal to the rank of g (C1020). The Introduction of J3/23-007r1 lists this among the new features. Neither installed compiler accepts it yet. This program is deliberately written in Fortran 2023 syntax and is rejected:

program remap_with_arrays
  implicit none
  integer, parameter :: lb(2) = [0, 10], ub(2) = [2, 13]
  real, target :: flat(12)
  real, pointer :: g(:, :)
  flat = 0.0
  g(lb:ub) => flat          ! Fortran 2023: bounds given as rank-one arrays
  print *, lbound(g), ubound(g)
end program remap_with_arrays

GNU Fortran 16.2 reports Error: Array index at (1) must be scalar, and LLVM Flang 23 reports error: Must be a scalar value, but is a rank-1 array on the same statement. Write the bounds as scalars until compilers catch up.

The contiguous attribute

contiguous (8.5.7) states that an array pointer can only be associated with a contiguous target, so that the compiler can generate unit-stride code and the pointer can be handed to code that assumes contiguity without a copy. The attribute is checked at the pointer assignment when the compiler can see the target, and the standard makes it a requirement on the program otherwise (10.2.2.3). This program is deliberately wrong:

program contiguous_ptr
  implicit none
  real, target :: a(10)
  real, pointer, contiguous :: p(:)
  integer :: i
  a = [(real(i), i = 1, 10)]
  p => a(2:5)                       ! fine: a simply contiguous section
  print '(a, l1, i3)', 'p => a(2:5):  is_contiguous=', is_contiguous(p), size(p)
  p => a(1:10:2)                    ! the target is not contiguous: not allowed for a contiguous pointer
  print '(a, i3)', 'p => a(1:10:2) accepted, size ', size(p)
end program contiguous_ptr

GNU Fortran 16.2 reports:

contiguous_ptr.f90:9:7:

    9 |   p => a(1:10:2)                    ! the target is not contiguous: not allowed for a contiguous pointer
      |       1
Error: Assignment to contiguous pointer from non-contiguous target at (1)

and LLVM Flang 23 reports error: CONTIGUOUS pointer may not be associated with a discontiguous target. is_contiguous(a) is true for rank-zero arguments, for whole arrays, for sections of a contiguous object with unit stride in the leading dimensions, and false for strided sections such as a(1:10:2) (the example in 16.9.115).

Pointer functions and pointers on the left of an assignment

A function whose result has the pointer attribute returns a pointer, and a reference to such a function is a variable (R902, C902): it denotes the target of the pointer it returns, and can therefore stand on the left of an assignment, be an actual argument for an intent(out) dummy, and so on (19.6.7). This is the Fortran way to write an accessor that returns a reference rather than a value. At the beginning of the function the result pointer has undefined status, and before it returns it shall be associated or nullified (9.7.1.4).

The same program shows the other classic use of pointers, double buffering. In a Jacobi sweep the new values are computed from the old ones, and the old array becomes the next iteration’s work array; swapping two pointers does this without moving data.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  integer, parameter, public :: dp = real64
end module heatflow_kinds

module heatflow_grid
  use heatflow_kinds, only: dp
  implicit none
  private
  public :: grid_t, cell, window

  type :: grid_t
    integer :: nx = 0, ny = 0
    real(dp), allocatable :: u(:, :)
  end type grid_t

contains

  ! A pointer function: the result is a pointer, so the reference denotes the target (a variable).
  function cell(g, i, j) result(p)
    type(grid_t), target, intent(inout) :: g
    integer, intent(in) :: i, j
    real(dp), pointer :: p
    p => g%u(i, j)
  end function cell

  function window(g, i0, i1, j0, j1) result(w)
    type(grid_t), target, intent(inout) :: g
    integer, intent(in) :: i0, i1, j0, j1
    real(dp), pointer :: w(:, :)
    w => g%u(i0:i1, j0:j1)
  end function window

end module heatflow_grid

program swap_and_function
  use heatflow_kinds, only: dp
  use heatflow_grid, only: grid_t, cell, window
  implicit none
  type(grid_t), target :: g
  real(dp), allocatable, target :: a(:, :), b(:, :)
  real(dp), pointer :: cur(:, :), nxt(:, :), tmp(:, :)
  integer :: step, i, j

  g%nx = 6
  g%ny = 6
  allocate (g%u(g%nx, g%ny), source=0.0_dp)

  cell(g, 3, 3) = 100.0_dp                       ! a function reference on the left of an assignment
  cell(g, 3, 3) = cell(g, 3, 3) / 4.0_dp
  window(g, 1, 2, 1, 2) = 1.5_dp                 ! array pointer function on the left
  print '(a, f7.2, a, f5.2)', 'u(3,3) = ', g%u(3, 3), '   u(2,2) = ', g%u(2, 2)

  ! Double buffering: swap two pointers instead of copying the arrays.
  allocate (a(8, 8), source=0.0_dp)
  allocate (b(8, 8), source=0.0_dp)
  a(4, 4) = 64.0_dp
  cur => a
  nxt => b
  do step = 1, 3
    nxt = cur                                    ! keeps the boundary values
    do j = 2, 7
      do i = 2, 7
        nxt(i, j) = 0.25_dp * (cur(i - 1, j) + cur(i + 1, j) + cur(i, j - 1) + cur(i, j + 1))
      end do
    end do
    tmp => cur                                   ! swap: three pointer assignments, no data moved
    cur => nxt
    nxt => tmp
    print '(a, i0, a, f8.3, a, l1)', 'after sweep ', step, ': max = ', maxval(cur), '   cur is a: ', associated(cur, a)
  end do
end program swap_and_function

Output (identical with GNU Fortran 16.2 and LLVM Flang 23):

u(3,3) =   25.00   u(2,2) =  1.50
after sweep 1: max =   16.000   cur is a: F
after sweep 2: max =   16.000   cur is a: T
after sweep 3: max =    9.000   cur is a: F

cell (g, 3, 3) = 100.0_dp and window (g, 1, 2, 1, 2) = 1.5_dp are legal only because the dummy argument g has the target attribute and intent(inout): the function result points into the caller’s object. After the swap, associated (cur, a) alternates, which shows that the pointers, not the arrays, moved. The swap is three pointer assignments instead of a whole-array copy, but the pointers may alias (see Aliasing and optimization), so the usual shape is a pure kernel with intent(in) and intent(inout) dummies that is called with cur and nxt, and a caller that does the swap.

Pointers as arguments

A pointer can be an actual argument, and a dummy argument can have the pointer attribute, the target attribute, both or neither. The combinations are the main source of surprises:

Dummy argument Actual argument and effect Clause

pointer (no intent)

the actual argument shall be a pointer; the dummy receives its association status, and the callee may re-associate or allocate it

15.5.2.8

pointer, intent(in)

the actual argument may be a pointer or any valid target; if it is not a pointer, the dummy is associated with it for the call; the callee may change the target’s value but not the association (C847)

15.5.2.8

pointer, intent(out)

the actual argument shall be a pointer and its association status becomes undefined on entry; the callee shall associate or nullify it

15.5.2.8, 8.5.10

target

a pointer associated with the actual argument before the call stays associated with the dummy when the actual is a target that is simply contiguous or the dummy is scalar, assumed-shape without contiguous or assumed-rank; pointers established inside the call (by p => dummy) remain valid after return in the same cases

15.5.2.5

target with a non-target actual or a vector-subscripted section

the pointers associated with the dummy become undefined when the call ends

15.5.2.5

target, value

pointers to the dummy become undefined at return (it is a copy)

15.5.2.5

neither pointer nor target

pointers associated with the actual argument before the call do not become associated with the dummy; modifying the actual through such a pointer during the call violates the no-aliasing rule (15.5.2.14), and a pointer set inside the call to the dummy can be invalid after return

15.5.2.5, 15.5.2.14

Where the dummy argument has target but is an explicit-shape array, an assumed-size array or has contiguous, and the actual argument is not simply contiguous, whether the pointers stay associated is processor dependent: a copy-in/copy-out may have happened. The advice is to give target dummies assumed-shape form and pass them contiguous actual arguments, or to avoid keeping pointers into dummy arguments past the call at all.

The next module has three procedures: a function-like subroutine that returns a scalar pointer to the hottest cell of a grid (the pointer dummy has intent(out)), a procedure that changes the target through a pointer dummy with intent(in), and a routine that returns a row pointer which stays valid after the call because its dummy has target.

module probes
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  integer, parameter :: dp = real64
  public :: locate_hottest, bump, view_row

contains

  ! pointer dummy argument, intent(out): the actual pointer becomes undefined on entry, then is set here
  subroutine locate_hottest(u, hottest)
    real(dp), intent(in), target :: u(:, :)
    real(dp), pointer, intent(out) :: hottest
    integer :: loc(2)
    loc = maxloc(u)
    hottest => u(loc(1), loc(2))
  end subroutine locate_hottest

  ! pointer dummy with intent(in): the target may be changed, the association may not
  subroutine bump(p, delta)
    real(dp), pointer, intent(in) :: p
    real(dp), intent(in) :: delta
    if (associated(p)) p = p + delta
  end subroutine bump

  ! target dummy argument: pointers made here may outlive the call
  subroutine view_row(u, i, row)
    real(dp), intent(inout), target :: u(:, :)
    integer, intent(in) :: i
    real(dp), pointer, intent(out) :: row(:)
    row => u(i, :)
  end subroutine view_row

end module probes

program dummies
  use, intrinsic :: iso_fortran_env, only: real64
  use probes
  implicit none
  real(real64), target :: field(3, 4)
  real(real64), pointer :: hot => null(), row(:) => null()
  integer :: i

  field = reshape([(real(i, real64), i = 1, 12)], [3, 4])
  call locate_hottest(field, hot)
  print '(a, f6.1)', 'hottest value: ', hot
  call bump(hot, 100.0_real64)
  print '(a, f6.1, a, f6.1)', 'field(3,4) after bump: ', field(3, 4), '   via pointer: ', hot

  call view_row(field, 2, row)
  row = 0.0_real64                      ! still associated with field after the call returned
  print '(a, 4f6.1)', 'row 2 of field after row = 0: ', field(2, :)
  print '(a, l1, a, 2i3)', 'associated(row, field(2, :)): ', associated(row, field(2, :)), '   lbound/ubound', &
    lbound(row, 1), ubound(row, 1)
end program dummies

Output (identical with GNU Fortran 16.2 and LLVM Flang 23):

hottest value:   12.0
field(3,4) after bump:  112.0   via pointer:  112.0
row 2 of field after row = 0:    0.0   0.0   0.0   0.0
associated(row, field(2, :)): T   lbound/ubound  1  4

The intent(in) on a pointer dummy applies to the association, not to the target (8.5.10). Re-pointing it is rejected, as this deliberately wrong module shows:

module m
  implicit none
contains
  subroutine retarget(p, t)
    real, pointer, intent(in) :: p
    real, target, intent(in) :: t
    p => t                           ! rejected: intent(in) forbids changing the association
  end subroutine retarget
end module m
intent_in_pointer.f90:7:5:

    7 |     p => t                           ! rejected: intent(in) forbids changing the association
      |     1~
Error: Dummy argument 'p' with INTENT(IN) in pointer association context (pointer assignment) at (1)

LLVM Flang 23 reports error: The left-hand side of a pointer assignment is not definable. Note also that view_row declares u as intent(inout), target: writing through a pointer to an intent(in) dummy is excluded by 15.5.2.14 (3b), so a target dummy that is modified through pointers cannot be intent(in).

Allocating and deallocating through pointers

allocate (p) or allocate (p(n)) creates an anonymous target with the target attribute and associates the pointer with it (9.7.1.4). deallocate (p) frees it and disassociates p. The statements have stricter rules for pointers than for allocatable variables, because the compiler cannot know what a pointer points at:

  • The pointer shall have a defined association status, and shall be associated with the whole of an object created by allocate (9.7.3.3): not with a variable, a section, or a part of an allocated array. This is the only deallocation a pointer is allowed.

  • deallocate of a disassociated pointer, or of a pointer whose target was not created by allocate, is an error condition (9.7.3.3). Without stat= the program terminates; with stat= the status is nonzero.

  • A pointer associated with an allocatable object shall not be deallocated.

  • allocate of a pointer that is already associated is not an error (9.7.1.4): a new target is created and the old association is dropped. If the old target had been allocated and no other pointer refers to it, it is leaked.

  • Deallocating a pointer target makes every other pointer associated with it, or with a part of it, undefined.

Dangling pointers, leaks and the errors the compiler can catch

A pointer is dangling when its target has gone but the pointer still holds the old association. This is the commonest pointer failure and the standard describes the pointer as undefined (19.5.2.5). There are three ways to dangle: the target is deallocated through another name (a second pointer or the allocatable variable), the target is a local variable of a procedure or block that returns or ends, and the target is a dummy argument without target whose association ends with the call.

This program is deliberately wrong. It deallocates the allocatable the pointer points at, then reads through the pointer.

program dangling
  implicit none
  real, allocatable, target :: buffer(:)
  real, pointer :: p(:)
  allocate (buffer(4), source=1.0)
  p => buffer
  deallocate (buffer)              ! the target is gone; p is now undefined, not disassociated
  print *, 'size(p) = ', size(p)
  print *, 'p(1)    = ', p(1)      ! reference to a dead target: undefined behaviour
end program dangling

Output with GNU Fortran 16.2 (the value is arbitrary; it can differ between runs and compilers):

 size(p) =            4
 p(1)    =    2.82147242E-40

LLVM Flang 23 printed size(p) = 4 and p(1) = -3.0914957E-13. Neither compiler’s run-time checks diagnose this, and neither did a warning. Valgrind does:

==12== Invalid read of size 4
==12==    by 0x400D03: MAIN__ (dangling.f90:9)
==12==    by 0x400D4B: main (dangling.f90:10)
==12==  Address 0x4d30a80 is 0 bytes inside a block of size 16 free'd
==12==    at 0x48884F8: free (vg_replace_malloc.c:989)
==12==    by 0x400C1F: MAIN__ (dangling.f90:7)
==12==    by 0x400D4B: main (dangling.f90:10)
==12==  Block was alloc'd at
==12==    at 0x488545C: malloc (vg_replace_malloc.c:446)
==12==    by 0x400B43: MAIN__ (dangling.f90:5)
==12==    by 0x400D4B: main (dangling.f90:10)

(the valgrind header and the library frames were removed). The error report names the line of the deallocate (dangling.f90:7) and of the allocation (dangling.f90:5), which is the quickest way to find the source of a dangling pointer. Valgrind ran inside the Linux container used for these examples. See Testing, Debugging and Profiling for the tools.

A leak is the opposite: the memory is still allocated and nothing points at it. The usual cause is re-allocating or re-pointing a pointer that owns its target.

program leak
  implicit none
  real, pointer :: p(:)
  allocate (p(1000))
  p = 1.0
  allocate (p(2000))               ! legal, but the first block is now unreachable
  p = 2.0
  print *, 'sum = ', sum(p)
  deallocate (p)
end program leak

Output with GNU Fortran 16.2 (identical with LLVM Flang 23, which formats the sum as 4000.):

 sum =    4000.00000

and valgrind --leak-check=full reports the first allocation as unreachable:

==12== HEAP SUMMARY:
==12==     in use at exit: 4,000 bytes in 1 blocks
==12==   total heap usage: 19 allocs, 18 frees, 17,564 bytes allocated
==12==
==12== 4,000 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12==    at 0x488545C: malloc (vg_replace_malloc.c:446)
==12==    by 0x400A6F: MAIN__ (leak.f90:4)
==12==    by 0x400CE7: main (leak.f90:10)
==12==
==12== LEAK SUMMARY:
==12==    definitely lost: 4,000 bytes in 1 blocks
==12==    indirectly lost: 0 bytes in 0 blocks
==12==      possibly lost: 0 bytes in 0 blocks
==12==    still reachable: 0 bytes in 0 blocks
==12==         suppressed: 0 bytes in 0 blocks

The program compiled warning-free, ran correctly and printed the right answer. Leaks are invisible without a tool, which is the best argument for allocatable: its memory is released automatically.

A local target that is pointed at from outside the procedure is a dangling pointer waiting to happen. GNU Fortran diagnoses the obvious case at compile time with -Wall:

module trap
  implicit none
  real, pointer :: saved_ptr(:) => null()
contains
  subroutine make()
    real, target :: local(3)
    local = [1.0, 2.0, 3.0]
    saved_ptr => local             ! local ceases to exist when make returns
  end subroutine make
end module trap

program use_trap
  use trap
  implicit none
  call make()
  print *, associated(saved_ptr)   ! status is undefined by the standard: do not rely on it
end program use_trap
local_target.f90:8:5:

    8 |     saved_ptr => local             ! local ceases to exist when make returns
      |     1~~~~~~~~~
Warning: Pointer at (1) in pointer assignment might outlive the pointer target [-Wtarget-lifetime]
 T

and the program then printed T for associated (saved_ptr), where the standard says that the pointer’s status is undefined (19.5.2.2 and Note 1 of 19.5.2.2). LLVM Flang 23 prints T too and gave no warning. Treat the value of associated on a pointer that may have dangled as meaningless.

The run-time errors a program meets while using pointers are these:

program bad_dealloc
  implicit none
  real, target :: x(3) = 0.0
  real, pointer :: p(:)
  p => x
  deallocate (p)                   ! p is associated, but its target was not created by ALLOCATE
end program bad_dealloc

Deallocating a pointer to a target variable (deliberately wrong) is not detected by -fcheck=all; GNU Fortran 16.2 prints

free(): invalid pointer

Program received signal SIGABRT: Process abort signal.

(followed by a backtrace; the free() message comes from the C library and the program is killed by SIGABRT). LLVM Flang 23 prints fatal Fortran runtime error(/work/bad_dealloc.f90:6): DEALLOCATE of a pointer that is not the whole content of a pointer ALLOCATE, which is more useful. Referencing a disassociated scalar pointer is a segmentation fault in both:

program null_ref
  implicit none
  real, pointer :: p => null()
  print *, 'associated: ', associated(p)
  print *, p                       ! reference to a disassociated pointer
end program null_ref
 associated:  F

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

but passing a disassociated array pointer to an inquiry function such as size is caught by -fcheck=pointer, which -fcheck=all enables:

program check_pointer
  implicit none
  real, pointer :: p(:) => null()
  print '(a, l1)', 'associated(p): ', associated(p)
  print '(a, i0)', 'size(p): ', size(p)          ! -fcheck=pointer catches this one
end program check_pointer
associated(p): F
At line 5 of file check_pointer.f90
Fortran runtime error: Pointer argument 'p' is not associated

Error termination.

(backtrace removed). Passing a disassociated pointer to size is not allowed by the standard (16.9.194 requires an associated pointer); LLVM Flang 23 tolerated it and printed size(p): 0, so the check is a feature of GNU Fortran, not a language guarantee.

Pointer or allocatable

Most programs that used pointers in the 1990s need allocatable today. An allocatable object owns its storage, is freed automatically when it goes out of scope, cannot be aliased by accident, and allows the compiler to assume that it does not overlap other objects. A pointer refers to storage somebody else owns, or to storage it owns by convention only, and the programmer, not the compiler, tracks the lifetime.

Need Use Why

A local or module array whose size is known at run time

allocatable

freed automatically; contiguous; no aliasing

A function result of variable size

allocatable function result

no pointer to free; works with pure

A derived-type component that holds an array

allocatable component

deep-copied by intrinsic assignment, freed with the object

A component that refers to an object owned elsewhere

pointer component (or class(*), pointer)

non-owning reference; the owner outlives it

A recursive type (list, tree)

allocatable component (Fortran 2008) or pointer

see Linked structures

A row, column, window or reshaped view of an existing array

pointer (or an assumed-shape dummy, if only a procedure needs it)

a view with no copy; the target needs the target attribute

Two buffers exchanged each iteration

pointer, or move_alloc

a swap of two pointers moves no data; move_alloc moves allocation without copying

A variable number of objects of different dynamic types

allocatable array of a wrapper type, or a polymorphic class(*), allocatable

avoids pointer chains

A reference that outlives a call and refers to a caller’s object

pointer, with a target dummy

documents that aliasing is intended

Data shared with C

pointer or c_ptr and c_f_pointer

interoperability, see Interoperability with C

Two Fortran 2003 and 2008 features removed most of the old reasons for pointers. Allocatable function results and allocatable components (TR 15581, in Fortran 2003) replaced pointer arrays as results and as structure members, and move_alloc moves an allocation from one variable to another without copying. Recursive allocatable components (Fortran 2008) replaced pointers in linked structures that have a single owner.

Linked structures

A linked list is the first structure every tutorial builds with pointers, and the first place their cost shows. The module below builds the same list of solver residuals twice: with pointer components, where ptr_clear has to walk the chain and free each node, and with a recursive allocatable component, where deallocate (b) frees the whole chain and move_alloc prepends a node without copying anything. A pointer is still useful in the allocatable version, to walk the list without deep-copying it.

module residual_lists
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  integer, parameter :: dp = real64
  public :: ptr_list_t, ptr_push, ptr_clear, alloc_node_t, alloc_push, alloc_sum

  ! 1) Classic pointer-based singly linked list: the programmer owns every node.
  type :: ptr_node_t
    real(dp) :: residual = 0.0_dp
    type(ptr_node_t), pointer :: next => null()
  end type ptr_node_t

  type :: ptr_list_t
    type(ptr_node_t), pointer :: head => null()
    integer :: count = 0
  end type ptr_list_t

  ! 2) The same list with a recursive allocatable component: the compiler owns every node.
  type :: alloc_node_t
    real(dp) :: residual = 0.0_dp
    type(alloc_node_t), allocatable :: next
  end type alloc_node_t

contains

  subroutine ptr_push(list, residual)
    type(ptr_list_t), intent(inout) :: list
    real(dp), intent(in) :: residual
    type(ptr_node_t), pointer :: node
    allocate (node)
    node%residual = residual
    node%next => list%head
    list%head => node
    list%count = list%count + 1
  end subroutine ptr_push

  subroutine ptr_clear(list)               ! without this, every node leaks
    type(ptr_list_t), intent(inout) :: list
    type(ptr_node_t), pointer :: node, following
    node => list%head
    do while (associated(node))
      following => node%next
      deallocate (node)
      node => following
    end do
    nullify (list%head)
    list%count = 0
  end subroutine ptr_clear

  subroutine alloc_push(head, residual)
    type(alloc_node_t), allocatable, intent(inout) :: head
    real(dp), intent(in) :: residual
    type(alloc_node_t), allocatable :: node
    allocate (node)
    node%residual = residual
    call move_alloc(head, node%next)       ! the old list becomes the tail
    call move_alloc(node, head)
  end subroutine alloc_push

  function alloc_sum(head) result(total)
    type(alloc_node_t), intent(in), target :: head
    real(dp) :: total
    type(alloc_node_t), pointer :: p
    total = 0.0_dp
    p => head                              ! a pointer is still the natural way to walk the list
    do
      total = total + p%residual
      if (.not. allocated(p%next)) exit
      p => p%next
    end do
  end function alloc_sum

end module residual_lists

program lists
  use, intrinsic :: iso_fortran_env, only: real64
  use residual_lists
  implicit none
  type(ptr_list_t) :: a
  type(alloc_node_t), allocatable :: b
  integer :: i

  do i = 1, 4
    call ptr_push(a, 1.0_real64 / i)
    call alloc_push(b, 1.0_real64 / i)
  end do
  print '(a, i0, a, f6.4)', 'pointer list: ', a%count, ' nodes, head residual ', a%head%residual
  print '(a, f8.5)', 'allocatable list sum: ', alloc_sum(b)


  call ptr_clear(a)
  print '(a, i0, l2)', 'after ptr_clear: count=', a%count, associated(a%head)
  deallocate (b)                           ! frees the whole chain node by node
end program lists

Output (identical with GNU Fortran 16.2 and LLVM Flang 23):

pointer list: 4 nodes, head residual 0.2500
allocatable list sum:  2.08333
after ptr_clear: count=0 F

valgrind --leak-check=full on the GNU Fortran executable reports All heap blocks were freed — no leaks are possible and no errors. In the pointer version, forgetting ptr_clear leaks all four nodes; in the allocatable version nothing can be forgotten, but there are two costs to know about. Freeing a very long chain recurses once per node, so a list with millions of nodes can overflow the stack on deallocate; and a node cannot be unlinked from the middle in constant time without a pointer to its predecessor.

The deep copy of a recursive allocatable chain is where GNU Fortran 16.2 currently fails. Intrinsic assignment of an object whose allocatable component is itself recursive must copy every level (10.2.1.3). With a chain of three nodes GNU Fortran copies the first two levels and shares the third, and freeing the copy then the original double-frees it. This program is deliberately written to show that defect; it is correct Fortran:

program rec2
  implicit none
  type :: node_t
    real :: v = 0.0
    type(node_t), allocatable :: next
  end type node_t
  type(node_t), allocatable :: b, c
  allocate (b)
  allocate (b%next)
  allocate (b%next%next)
  c = b
  print *, 'copied'
  deallocate (c)
  print *, 'freed c'
  deallocate (b)
  print *, 'freed b'
end program rec2
 copied
 freed c

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

(after freed c, GNU Fortran 16.2 terminates with SIGSEGV; the run under valgrind reports an invalid free in the generated deallocation routine). LLVM Flang 23 prints copied, freed c, freed b. With GNU Fortran, copy a recursive structure with a user routine that walks the chain, or do not copy it.

Aliasing and optimization

Fortran’s reputation for speed rests on a rule about aliasing. A dummy argument without the pointer or target attribute must not be modified through any other name while the procedure runs, and its storage may not be changed by the allocation status of another name (15.5.2.14). The compiler may therefore assume that two such array dummies do not overlap, vectorize loops without run-time overlap tests and keep values in registers. A target object, or anything reached through a pointer, may be aliased, and the compiler has to be conservative with it. The practical consequences:

  • Do not declare variables target or pointer unless you need to; the attribute removes the optimizer’s best assumption.

  • Prefer assumed-shape dummies with intent over pointer dummies in performance-critical kernels, as the heatflow kernels jacobi_sweep and ftcs_sweep do, which keeps the no-aliasing guarantee.

  • Pointers to non-contiguous sections need strided access; contiguous on the pointer lets the compiler assume unit stride.

  • Two pointers that may be associated with the same target, as p and q in the first example, are legal and are the reason a sweep that writes through one and reads through the other cannot be reordered.

The cost of aliasing is the subject of Performance and Memory Layout.

Where the books are outdated

The two Fortran books consulted for this section treat pointers as the standard tool for dynamic data. That was true of Fortran 90 and 95 and is no longer the right default. Where they differ from current practice:

Area Outdated in the books, current approach

Dynamic arrays

pointer arrays for work arrays, function results and structure components; allocatable arrays, allocatable function results and allocatable components (Fortran 2003 and 2008) are safer, cannot leak and are at least as fast

Linked structures

linked lists and trees only with pointers; recursive allocatable components (Fortran 2008) give single-owner structures that free themselves, and move_alloc rearranges them without copying

Aliasing

pointers described as the way to get a view of an array; associate, assumed-shape dummies and block constructs often replace them, and target is kept for the cases that need pointers

Initialization

explicit nullify statements at the start of every procedure; => null() in declarations and default component initialization make the pointer disassociated from the start

Remapping

pointer rank and bounds remapping is shown with rank-one targets only (Fortran 2003); Fortran 2008 allows any simply contiguous target, and Fortran 2023 allows bounds given as arrays

Function results

a pointer function result is described as a value; a reference to a pointer function is a variable and can be assigned to (Fortran 2008)

contiguous and is_contiguous

not covered (Fortran 2008): a pointer cannot be passed to a contiguous assumed-shape dummy without a possible copy otherwise

Procedure pointers

covered with the Fortran 2003 object-oriented features; type-bound procedures replace many uses

Compiler context

no compiler-specific behaviour; on this page GNU Fortran 16.2 and LLVM Flang 23 differ on array pointer initialization and on deep copies of recursive allocatable components

Standard version provenance and compiler support

Feature Introduced Compiler support checked on 2026-10-18

pointer, target, allocate and deallocate of pointers, nullify, associated, pointer components

Fortran 90

all compilers

null(), => null() initialization, default initialization of pointer components

Fortran 95

GNU Fortran 16.2 and LLVM Flang 23

Bounds-spec and bounds-remapping in pointer assignment, intent on pointer dummies, procedure pointers, allocatable function results and components

Fortran 2003

both

contiguous, is_contiguous, initial data targets, pointer function results as variables, remapping of simply contiguous targets, recursive allocatable components

Fortran 2008

both, except the array case of initial data targets in GNU Fortran 16.2 and the three-level copy of recursive allocatable components in GNU Fortran 16.2

Bounds in pointer assignment given as rank-one arrays

Fortran 2023 (J3/23-007r1, Introduction and C1020)

rejected by GNU Fortran 16.2 and LLVM Flang 23

Not checked 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.

Pitfalls

  • Forgetting target. A pointer assignment to a variable without the attribute is rejected only when the compiler can see it. Through a dummy argument without target it is accepted and the pointer becomes undefined at return (15.5.2.5).

  • Using = where => is meant. p = t on a disassociated p is a crash, and on an associated p it overwrites the target.

  • Pointer variables without an initializer. They are undefined, not disassociated, and associated may not even be asked about them; write => null().

  • Believing associated detects a dangling pointer. It cannot: the status is undefined after the target goes away.

  • Deallocating a pointer that points at a variable, a section or part of an allocation. It is an error; only deallocate of a pointer to the whole of an allocated object is allowed.

  • Copying a derived type with pointer components. Intrinsic assignment copies the pointers, so two objects share their targets, and deallocate of one invalidates the other.

  • Returning a pointer to a local variable. The local ceases to exist at return.

  • Pointer actual argument for a non-pointer dummy. The dummy argument receives the target by reference; an undefined or disassociated pointer is an error at the call.

  • Relying on pointers to survive a call to a target dummy with a non-contiguous actual argument. Whether they do is processor dependent.

  • Writing through a pointer to an intent(in) dummy. It is excluded by 15.5.2.14.

  • Large pointer-based lists. Allocate per node is slow; consider an array of nodes with integer indices, or an allocatable array.

  • Pointer arrays for work space. They leak, are not freed at return and make the compiler assume aliasing (gfortran and Flang then add a run-time overlap check, see Performance and Memory Layout); use allocatable or automatic arrays.

See also Best Practices and Style and Common Gotchas and Pitfalls, and, for the replacement of equivalence by pointers, Legacy Features and Modernization.