Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

defer for cleanup

A procedure with three returns after its allocate needs three deallocates, and the fourth return someone adds later leaks. defer writes the release once, on the line after the allocation, and runs it on every way out of the block. The statement itself is introduced in defer; this page shows it paired with memory.

'' deallocate, and say so, so the program shows when it happens.
sub Release( byval p as any ptr )
    deallocate( p )
    print "  freed"
end sub

'' The squares 1, 4, 9 ... of the first n numbers, summed through a
'' scratch buffer. Every way out after the allocate frees it.
function SumOfSquares( byval n as int32 ) as int64
    if n < 1 then return 0                  '' nothing allocated yet

    dim as int64 ptr squares = allocate( n * sizeof( int64 ) )
    defer Release( squares )                '' written once, beside it
    if squares = nullptr then return -1     '' freeing null is harmless

    for i as int32 = 0 to n - 1
        squares[i] = ( i + 1 ) * ( i + 1 )
    next
    if n > 1000 then return -2              '' another way out

    dim as int64 total = 0
    for i as int32 = 0 to n - 1
        total += squares[i]
    next
    return total
end function

dim sizes( 0 to 2 ) as int32 = { 0, 3, 2000 }
for each n in sizes
    print "n ="; n
    var sum = SumOfSquares( n )
    print "  result"; sum
next

Output

n = 0
  result 0
n = 3
  freed
  result 14
n = 2000
  freed
  result-2

Release frees the block and prints freed, so the output shows when the deferred call runs.

  • With n = 0 the function returns before the allocate, and before the defer line is reached, so nothing is freed: a defer only takes effect once the program has passed it.
  • With n = 3 the function reaches its last return, and the block is freed on the way out.
  • With n = 2000 it leaves early through return -2, and the block is freed all the same.

Three habits make this reliable:

  • Write the defer on the line after the allocate, before the check for null. deallocate( nullptr ) does nothing, so the failure path is covered too, and the pair cannot drift apart.
  • The deferred statement reads its variables when it runs, at the end of the block, not when the defer line is reached. If a reallocate stores a new address in squares, the right block is still freed.
  • Allocate and defer in the procedure's own body, not inside an if. A defer runs at the end of the block it is written in, so inside an if it frees the memory at end if.

defer suits a resource used in one procedure. When the same kind of resource is acquired in many places, a type with a destructor puts the release where it cannot be forgotten.

Activities

  1. Add a second scratch buffer to SumOfSquares with its own defer. In which order are the two freed?
  2. Move the allocate and its defer inside an if n > 0 then ... end if block. Where does freed appear now?