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, |
8.5.8 |
|
an entity cannot be pointer and allocatable, pointer and target, or a pointer coarray (C854, C866) |
8.5.14 |
|
an object without the |
8.5.18 |
Subobjects inherit |
if an object has |
8.5.18 |
Allocated pointer targets are targets |
the object created by |
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 |
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.
The events themselves are listed in the standard; this table groups the ones a programmer meets:
| Result | Events | Clause |
|---|---|---|
becomes associated |
|
19.5.2.3 |
becomes disassociated |
|
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 |
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 |
|---|---|
|
extents from the target; lower bound from |
|
same extents, the stated lower bounds |
|
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 |
|
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 |
|---|---|---|
|
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 |
|
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 |
|
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 |
|
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 |
15.5.2.5 |
|
the pointers associated with the dummy become undefined when the call ends |
15.5.2.5 |
|
pointers to the dummy become undefined at return (it is a copy) |
15.5.2.5 |
neither |
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. -
deallocateof a disassociated pointer, or of a pointer whose target was not created byallocate, is an error condition (9.7.3.3). Withoutstat=the program terminates; withstat=the status is nonzero. -
A pointer associated with an allocatable object shall not be deallocated.
-
allocateof 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
Source: GNU Fortran manual: Options to request or suppress errors and warnings and J3/23-007r1: 19.5.2 Pointer association (19.5.2.2 to 19.5.2.5)
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.
Source: J3/23-007r1: 9.7.1.4 Allocation of pointer targets, 9.7.3.3 Deallocation of pointer targets, 19.5.2.5 Events that cause the association status of pointers to become undefined, GNU Fortran manual: Options to request or suppress errors and warnings, GNU Fortran manual: Options for code generation conventions
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 |
|
freed automatically; contiguous; no aliasing |
A function result of variable size |
|
no pointer to free; works with |
A derived-type component that holds an array |
|
deep-copied by intrinsic assignment, freed with the object |
A component that refers to an object owned elsewhere |
|
non-owning reference; the owner outlives it |
A recursive type (list, tree) |
|
|
A row, column, window or reshaped view of an existing array |
|
a view with no copy; the target needs the |
Two buffers exchanged each iteration |
|
a swap of two pointers moves no data; |
A variable number of objects of different dynamic types |
|
avoids pointer chains |
A reference that outlives a call and refers to a caller’s object |
|
documents that aliasing is intended |
Data shared with C |
|
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
targetorpointerunless you need to; the attribute removes the optimizer’s best assumption. -
Prefer assumed-shape dummies with
intentover pointer dummies in performance-critical kernels, as the heatflow kernelsjacobi_sweepandftcs_sweepdo, which keeps the no-aliasing guarantee. -
Pointers to non-contiguous sections need strided access;
contiguouson the pointer lets the compiler assume unit stride. -
Two pointers that may be associated with the same target, as
pandqin 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 |
Aliasing |
pointers described as the way to get a view of an array; |
Initialization |
explicit |
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) |
|
not covered (Fortran 2008): a pointer cannot be passed to a |
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 |
|---|---|---|
|
Fortran 90 |
all compilers |
|
Fortran 95 |
GNU Fortran 16.2 and LLVM Flang 23 |
Bounds-spec and bounds-remapping in pointer assignment, |
Fortran 2003 |
both |
|
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 withouttargetit is accepted and the pointer becomes undefined at return (15.5.2.5). -
Using
=where=>is meant.p = ton a disassociatedpis a crash, and on an associatedpit overwrites the target. -
Pointer variables without an initializer. They are undefined, not disassociated, and
associatedmay not even be asked about them; write=> null(). -
Believing
associateddetects 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
deallocateof 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
deallocateof 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
targetdummy 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
allocatableor 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.