skip to content

Under copy-restore parameter passing, when does the result differ from call by reference for the same call?

level: middleimportance: nice to knowfreq 24%

answer

  1. copy in at the call, back at the return
  2. identical until something aliases
  3. the same variable in two slots
  4. restore order decides the survivor
  5. mid-call observers see different values

basics

~20 s

They diverge whenever a second route reaches the same storage during the call. Copy-restore works on a private copy and writes back once at the return, so aliased writes are lost or overwritten and timing differs; call by reference makes every write land immediately.

solid answer

~50 s

**Copy-restore** copies the argument's value into the parameter at the call and copies the parameter's final value back into the caller's variable at the return. With no aliasing the caller cannot tell it from **call by reference**: the variable ends up with the same value either way. They separate when a second route reaches the same storage while the call is running — the same variable passed in two slots, or a variable the routine can also reach directly. Under call by reference every write lands immediately and the last write wins. Under copy-restore the routine has independent copies, so the write-backs at the return decide the outcome, in an order the language must define, and one slot's work can silently erase another's. Timing differs too: anything observing the caller's variable mid-call sees the new value under reference and the old one under copy-restore.

code

pseudocode · 8 lines
pseudocode
procedure blend(x, y)
    x = 1
    y = 2
    print x        // by reference: 2    copy-restore: 1

v = 0
blend(v, v)        // one variable, two parameter slots
print v            // by reference: 2    copy-restore: 1 or 2, by restore order

go deeper

for a junior

Recall the shape only: copy the value in at the call, copy it back out at the return. That is enough to see why the caller's variable changes once rather than continuously.

for a middle

Explain the divergence. Walk the same-variable-in-two-slots example in both modes and say which write survives and why, then name the timing difference a mid-call observer would see.

for a senior

Generalise the hazard. Aliasing between two routes to one piece of storage is a live bug class wherever two names can reach one record, and knowing which writes are visible when is what lets you reason about it.

for a principal

Use it as an interface rule: decide where your codebase permits two parameters to reach the same storage at all, and make the ownership of a passed record explicit rather than leaving each caller to discover the aliasing case.

**Copy-restore** — also called copy-in/copy-out, or by value-result — is the parameter mode that copies the argument's value into the parameter at the call and copies the parameter's final value back into the caller's variable at the return. It exists because it delivers the *effect* people want from call by reference (the caller sees the update) while keeping the *implementation* of call by value (the routine works on local storage, addressed directly, with no indirection on every access). ## The case where the two are indistinguishable One variable, one slot, nothing else reaching it. The routine assigns, the caller's variable ends up with the assigned value, and no observer looks in between. Copy-restore and call by reference agree, and that agreement is the whole reason the mode is usable at all. ## The three places they diverge **1. Aliasing.** Two routes to one piece of storage. - Under **call by reference**, both names *are* that storage. Every write is visible through the other name at once, and the final value is simply the last write executed. - Under **copy-restore**, each slot is an independent copy. The routine's writes do not see each other. At the return the copies are written back, and whichever is written back **last** decides the caller's final value — which means one slot's work can silently erase the other's. The classic demonstration passes the same variable in two slots: ``` procedure blend(x, y) x = 1 y = 2 print x v = 0 blend(v, v) print v ``` Under call by reference `x` and `y` both name `v`: `x = 1` makes it 1, `y = 2` makes it 2, so the routine prints **2** and the caller prints **2**. Under copy-restore the routine holds two copies, so it prints **1**; at the return one copy is restored and then the other, and the caller prints **1** or **2** depending on the restore order the language defines. Same source, three possible outcomes across two modes — which is exactly why aliasing is the thing to name. The second, subtler alias is a variable the routine can reach *both* through a parameter and directly. Then even one slot is enough to produce the divergence. **2. Timing.** Under call by reference the caller's variable changes at the moment of each assignment. Under copy-restore it changes once, at the return. Anything that can look at the variable while the call is in progress — another routine reachable from inside the call, a handler that runs mid-call — sees the new value in one mode and the old value in the other. **3. Abnormal exit.** Copy-restore's write-back is an action performed as part of returning. If the routine does not return normally, whether the copies are written back at all is a question the language has to answer. Call by reference has no such question: whatever was written is already written. | | Call by reference | Copy-restore | |---|---|---| | What the slot holds | the caller's storage location | a private copy of the value | | When the caller's variable changes | at each assignment | once, at the return | | Same variable in two slots | one storage, last write wins | two copies, restore order wins | | Cost per access inside the routine | an indirection | a direct local access | | Cost at the boundary | one location copied | the value copied in and out | ## A related mode with a sharper surprise **Call by name** substitutes the unevaluated argument expression and re-evaluates it at **every use** of the parameter. If the argument is an expression naming an indexed element and the routine changes the index between two uses, the two uses read *different* storage — the parameter follows the index rather than naming one thing. **Jensen's device** turns exactly that into a feature, passing an expression and the variable it mentions so that a single parameter yields a whole series of values as the routine drives the variable. The same mechanism is a trap when the argument has a side effect: it runs once per use, not once per call. ## Why this is worth knowing Nobody is asking you to pick copy-restore for a design. The value is that it makes the general rule concrete: **a parameter mode is fully described by what the call copies and when it copies it back.** Once you can say that for four modes, the everyday question — "is this by reference?" — stops being a matter of opinion. It also gives you the vocabulary for a real class of bug: two parameters that unexpectedly name the same thing, which is a hazard in any language that lets two names reach one piece of storage, whatever it calls its modes.

  • In call by name the argument expression is re-evaluated at every use. What surprise does that create?
    Two uses of the same parameter can read different storage, because the expression is re-evaluated against whatever its variables hold at that moment — an indexed element follows the index as the routine changes it. An argument with a side effect runs once per use rather than once per call. Jensen's device deliberately exploits the first behaviour.
  • If a routine never assigns to a parameter, can copy-restore be distinguished from call by value?
    Usually not: with no assignment the value copied back equals the value copied in. The exception is when something else changes the caller's variable during the call. Copy-restore then writes the entry-time copy back over that change, silently undoing it, while call by value leaves the variable alone.
  • What does copy-restore buy over call by reference, given the aliasing hazard?
    Direct local access. The parameter is ordinary storage in the frame, so every read and write inside the routine avoids the indirection a reference costs, and the compiler can reason about the local without worrying that another name touches it. The whole cost is paid twice at the boundary instead of once per access.

saying these in an interview costs you the question

  • Says copy-restore and call by reference can never be told apart
  • Assumes the restore order is universal, so the winner is predictable
  • Believes copy-restore writes back at every assignment rather than at the return
  • Thinks passing one variable in two slots is meaningless, so aliasing never arises
  • Claims the write-back happens even when the routine exits abnormally