Recursion and computational thinking · A level · OCR H446 2.2.1, AQA 7517 4.1.1.15 · about 20 min
Return addresses, parameters and local variables: what a subroutine call pushes, and what a return pops.
[1 mark]Which of these does a stack frame store for a subroutine call?
Tick every answer that is true.
[1 mark]What happens to the call stack when a subroutine returns?
[1 mark]Why is a stack the right structure for subroutine calls?
[1 mark]What does this program print?
def inner():
total = 100
return total
def outer():
total = 5
inner()
return total
print(outer())5
inner's total is a local in a separate frame, which is popped when inner returns, so outer's total is still 5. The value inner returned was not used.
[1 mark]main calls a(), a() calls b(), and b() calls c(). Counting a frame for the main program, how many frames are on the call stack while c() runs?
[1 mark]What is the name of the error when a chain of calls uses up all the memory set aside for the call stack?
The starter drives the robot through lap(10), which calls corner twice, and each corner calls beep. Make the call stack visible.
- Keep a list called stack. At the start of every subroutine, push a frame onto it: a dictionary with the keys "name", "params" (a dictionary of parameter name to value) and "return_to" (the name of the caller, or "main" for the call from the main program). Then print push <name> <parameter>=<value>, return to <caller>, depth <d>, where <d> is the number of frames on stack after the push.
- At the end of every subroutine, pop the top frame and print pop <name>, depth <d>, where <d> is the number of frames left.
- Keep track of the deepest the stack gets, and after lap(10) has finished print deepest: <n>.
The first line is push lap size=10, return to main, depth 1 and the last before deepest is pop lap, depth 0. The robot still drives and beeps as before.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
stack = []
def beep(freq):
tone(freq, 0.2)
def corner(cm):
forward(50, distance=cm)
beep(880)
def lap(size):
corner(size)
corner(size)
lap(10)The hint students can ask for: Every subroutine needs the same two jobs: one at its first line and one just before it ends. Write those two jobs once as helpers. The depth is simply how many frames are on the list at that moment, and the deepest is the largest that ever gets.
from bugbot import *
connect()
stack = []
deepest = 0
def push(name, param, value, return_to):
global deepest
stack.append({"name": name, "params": {param: value}, "return_to": return_to})
deepest = max(deepest, len(stack))
print(f"push {name} {param}={value}, return to {return_to}, depth {len(stack)}")
def pop():
frame = stack.pop()
print(f"pop {frame['name']}, depth {len(stack)}")
def beep(freq):
push("beep", "freq", freq, "corner")
tone(freq, 0.2)
pop()
def corner(cm):
push("corner", "cm", cm, "lap")
forward(50, distance=cm)
beep(880)
pop()
def lap(size):
push("lap", "size", size, "main")
corner(size)
corner(size)
pop()
lap(10)
print("deepest:", deepest)
Any program that meets the task's checks is marked correct in the simulator; this is one way, not the only way.