Exercises: Pointers, Targets, and Dynamic Data Structures
These exercises drill the two habits this chapter is really about: getting => versus = right every
time, and knowing when not to reach for a pointer at all. Several ask you to diagnose a bug — pointer
bugs are subtle and silent, and learning to see them on the page is a skill worth more than any syntax.
Type, compile, and run the code ones; the discipline of predicting output before you compile is where the
learning happens.
Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†)
and odd-numbered problems are in appendices/answers-to-selected.md; the computational ones (11.11, 11.20,
11.27, 11.28) are also worked as compilable code in code/exercise-solutions.f90. Try every problem before
you look.
Part A — Warm-ups ⭐
11.1 † Predict the exact output, then explain each line in terms of "alias" versus "value":
real(dp), target :: x = 3.0_dp, y = 7.0_dp
real(dp), pointer :: p => null()
p => x
p = 10.0_dp
p => y
print '(2f6.1)', x, y
11.2 What attribute must an object carry before a pointer is allowed to point at it? State the reason the compiler insists on it (hint: what can it assume about an object that lacks the attribute?).
11.3 † Name the three association statuses a pointer can have. Which one must you never pass to the
associated intrinsic, and what happens if you do?
11.4 In one sentence each, distinguish nullify(p) from deallocate(p). Which one frees memory?
11.5 † True or false, with one sentence of justification: "The statement p = a changes what p points
to."
Part B — Association Status and the Two Hazards ⭐⭐
11.6 The fragment below has a latent bug that no amount of testing can reliably catch. Name it and give the one-line fix.
real(dp), pointer :: p
if (associated(p)) call use_it(p) ! p was never initialized
11.7 † Explain precisely what is wrong with this fragment, why associated(q) may return .true.
anyway, and what the correct cleanup is:
allocate(p)
q => p
deallocate(p)
print *, q ! <-- ?
11.8 Write the two-character-per-declaration habit (show it in code) that guarantees you can never have an undefined pointer in your program.
11.9 † Give a concrete scenario in which associated(p) returning .true. is not enough information,
and associated(p, target) is the test you actually need.
Part C — Type, Compile, and Run ⭐⭐
11.10 Compile and run code/example-02-linked-list.f90. Then change prepend into an append (add
each new node at the tail instead of the head). Predict the new values head->tail: line and the sum for
pushing 1, 2, 3, then confirm.
11.11 † Add an integer function list_length(head) that counts the nodes of the linked list without
modifying it (walk a local traversal pointer; do not touch head). Predict its return value for the
three-node list. (Worked as code in code/exercise-solutions.f90 in spirit; see the show routine's
walk.)
11.12 For the 4×4 matrix a(i,j) = 10*i + j, predict the value of is_contiguous(a(:,3)) and
is_contiguous(a(2,:)), and the sum of a(:,3). Which of the two sections may a contiguous pointer
legally alias?
Part D — Find the Bug ⭐⭐
11.13 † This list-destroying loop crashes (or silently corrupts memory). Diagnose the fault and fix the ordering:
p => head
do while (associated(p))
deallocate(p)
p => p%next
end do
11.14 A function returns a pointer to a result it computed:
function bad() result(r)
real(dp), pointer :: r
real(dp), target :: local = 42.0_dp
r => local
end function bad
Why does the returned pointer dangle the moment the caller uses it? What owns local, and how long does it
live?
11.15 † What happens if you execute p = 5.0_dp when p is disassociated (not pointing at anything)?
Why is this different from the => case, and how do you guard against it?
11.16 An author gives a derived type a pointer component and is surprised that after b = a, writing
to b%u also changes a%u. Explain what default assignment does with a pointer component, and give the
one-word change that makes b an independent copy.
Part E — allocatable vs pointer: Port and Modernize ⭐⭐
11.17 † Modernize. A routine uses a pointer purely as a local scratch array:
real(dp), pointer :: work(:)
allocate(work(n))
! ... use work ...
deallocate(work)
Rewrite it with allocatable and list every concrete advantage the change buys (there are at least three).
11.18 Port it. This Python builds a list and sums it:
buf = []
for k in range(1, 6):
buf.append(k * k)
print(sum(buf))
Write the Fortran a professional would ship — using a growable allocatable array, not a linked list — and explain in one sentence why the array is the right Fortran choice.
11.19 † State the one-sentence rule for choosing between allocatable and pointer. Then classify each
use as one or the other, with a reason: (a) a local scratch array; (b) a frequently-restructured graph;
(c) a handle to an array returned by a C library; (d) a container meant to hold objects of several dynamic
types; (e) a run-time-sized field bundled inside a derived type.
Part F — Design It: The Solver and Dynamic Structures ⭐⭐⭐
11.20 † Design a buffer_t module with an allocatable data array and a push type-bound procedure
that doubles capacity when full, using move_alloc for the hand-off. Give the capacity after each of five
pushes. (Worked as code in code/exercise-solutions.f90.)
11.21 Add a contiguous assumed-shape dummy argument to a sweep subroutine:
subroutine sweep(u)
real(dp), intent(inout), contiguous :: u(:,:)
! ...
end subroutine
What promise must every caller keep, and what is the compiler now permitted to do that it could not for a
plain assumed-shape u(:,:)? (This is the Chapter 29 optimization, previewed.)
11.22 † In exactly three sentences — one per reason — explain why the heat solver's field_t stores its
grid as real(dp), allocatable :: u(:,:) and not as a pointer component. Then state, in a fourth sentence,
the one situation in this book where the solver does legitimately use a pointer.
Part G — Interleaved and Back of the Envelope ⭐⭐⭐
11.23 † (Ch. 5) In Fortran's column-major layout, which of a(:,k) and a(k,:) is contiguous in
memory, and why? Which one may a contiguous pointer alias?
11.24 (Ch. 9) Why does a field_t with an allocatable component u produce an independent copy on
b = a, while a pointer component would make b%u alias a%u?
11.25 † (Ch. 6) A subroutine declares real(dp), pointer, intent(in) :: v(:). Does intent(in)
constrain the pointer's association, its target's values, or both? Explain what you may and may not do
with v inside the routine.
11.26 (Ch. 8) You place buffer_t and its push in a module. Why does the module hand you an
explicit interface to push "for free," and why does a type-bound procedure taking a class(buffer_t)
argument require that explicit interface?
11.27 † Back of the envelope. A linked list stores $n = 10^{7}$ four-byte integers, one per node, each
node also holding an 8-byte next pointer (call it 16 bytes per node after padding). How much memory does
the list use versus a plain integer array of the same $n$? Give the ratio. (Worked in
code/exercise-solutions.f90.)
11.28 Back of the envelope. A growable array doubles its capacity whenever it fills. Over $m$ pushes
starting from capacity 1, roughly how many element-copies happen in total (order of magnitude in $m$), and
therefore what is the amortized per-push cost? (Worked in code/exercise-solutions.f90.)
Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md. The
"type, compile, and run" and "find the bug" problems reward predicting the result — or the failure — before
you touch the compiler; a pointer bug you can spot on paper is one you will never ship.