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.