Intuition What this page is
The parent note taught the three moves — D eclare, D efine, C all — and the one law
that governs every call: call by value copies the argument . This page stress-tests that law
against every situation C can hand you. We enumerate the cases first, then walk each one until
you can predict the output of any call-by-value program without running it.
Return here from the Hinglish note or the parent topic whenever you doubt "did the original change?".
Every call-by-value question is really one question: did the callee touch the caller's real memory, or only a photocopy? The answer depends on what you copied. Here is the full grid of cases this topic throws at you.
#
Case class
What is copied
Does caller's data change?
A
Plain int value, callee reassigns it
the number
No
B
Two values swapped inside callee
the two numbers
No (classic swap trap)
C
Pointer passed, callee dereferences
the address
Yes (edits through the copy)
D
Pointer passed, callee reassigns the pointer itself
the address
No — the pointer copy moves
E
Array passed to a function
the address of element 0
Yes (arrays decay to pointers)
F
Zero / degenerate input (0, NULL)
a value / null address
depends — must guard
G
Return value ignored vs used
nothing leaks; copy dies
caller only sees what it stores
H
Word problem (real world)
domain values
model it, then apply A–G
I
Exam twist: prototype/definition mismatch
—
compile error / silent bug
The six cases A, B, C, D are the mechanics ; E, F, G are the edge conditions ; H, I are application and traps . The worked examples below hit every cell .
int bump ( int x ) {
x = x + 100 ; // reassigns the local copy
return x;
}
int main ( void ) {
int k = 5 ;
int r = bump (k);
printf ( " %d %d\n " , r, k);
return 0 ;
}
What does this print?
Forecast: Guess the two numbers before reading on.
k is created in main holding 5.
Why this step? A variable is just a named box in main's stack frame. See the left box in the figure.
bump(k) copies the value 5 into a brand-new box named x inside bump's frame.
Why this step? Call by value: the parameter is a fresh local, initialised from the argument. x and k are two different boxes that happen to start equal.
x = x + 100 changes only the x box → 105.
Why this step? Assignment writes to the box on its left. That box lives in bump's frame; k cannot feel it.
return x copies 105 back; r stores it. bump's frame is destroyed.
Why this step? The return value is itself a copy handed to the caller.
Answer: prints 105 5.
Verify: r = 5 + 100 = 105. k was never on the left of an assignment inside main after init, so k = 5. Units: both are counts, consistent. ✓
void swap ( int x , int y ) {
int t = x; x = y; y = t;
}
int main ( void ) {
int a = 3 , b = 8 ;
swap (a, b);
printf ( " %d %d\n " , a, b);
}
What does this print?
Forecast: Many say 8 3. Predict, then check.
a=3, b=8 live in main's frame. Two real boxes.
Why this step? Establish where the originals are.
swap(a,b) copies 3→x, 8→y into swap's frame.
Why this step? Two arguments → two independent copies. Nothing links x back to a.
Inside, t=3, then x=8, then y=3. The copies genuinely swap.
Why this step? The logic is correct — but it operates entirely in swap's scratchpad.
swap returns; its frame (with the swapped x,y) is destroyed.
Why this step? The swap happened, then evaporated. Nothing was written to a or b.
Answer: prints 3 8 — unchanged .
Verify: a and b never appeared on the left of an assignment in main; therefore they equal their initial values 3 and 8. The swap of x,y is a fact that lived and died in another frame. ✓
void swap ( int * x , int * y ) {
int t = * x; * x = * y; * y = t;
}
int main ( void ) {
int a = 3 , b = 8 ;
swap ( & a, & b);
printf ( " %d %d\n " , a, b);
}
What does this print?
Forecast: Guess before reading.
&a and &b are the addresses of the two boxes — say a lives at address 1000, b at 1004.
Why this step? & reads "address of". An address is itself just a number we can copy.
swap(&a,&b) copies those two address-numbers into x and y.
Why this step? Still call by value! We copied 1000 and 1004. But a copy of an address points to the same box as the original.
*x means "the box at address x", i.e. the real a. So t=*x=3, then *x=*y writes 8 into a's box, then *y=t writes 3 into b's box.
Why this step? Dereferencing (*) reaches back through the copied address to the caller's memory.
swap returns; the address copies die, but the writes they performed are permanent.
Why this step? We edited a and b directly; nothing was undone by the frame's destruction.
Answer: prints 8 3 — really swapped.
Verify: After the three lines a holds the old b (8) and b holds the old a (3). Values swapped; consistent. ✓
See Pointers in C and Stack and Function Call Mechanism for the memory picture in depth.
void tryRedirect ( int * p ) {
int local = 99 ;
p = & local; // change the COPY of the address
* p = 42 ; // writes to local, not to caller
}
int main ( void ) {
int a = 3 ;
tryRedirect ( & a);
printf ( " %d\n " , a);
}
What does this print?
Forecast: Trap alert — does a become 42? 99? 3?
&a (say 1000) is copied into p. Now p == 1000, pointing at a.
Why this step? Same address-copy as Example 3.
p = &local overwrites the copy p with local's address.
Why this step? This is assignment to p, which is a local box in the callee. main's &a (which was passed) is not affected — only our copy moved.
*p = 42 now writes 42 into local, not a.
Why this step? p no longer points home; dereferencing reaches local.
Function returns; local and the moved p die.
Why this step? Nothing ever wrote to a after step 1.
Answer: prints 3.
Verify: a was passed by address, but we redirected the pointer before writing. The write hit local. a = 3, unchanged. Contrast with Example 3 where we dereferenced first . ✓
void triple ( int arr [] , int n ) {
for ( int i = 0 ; i < n; i ++ ) arr [i] = arr [i] * 3 ;
}
int main ( void ) {
int v [ 3 ] = { 2 , 4 , 6 };
triple (v, 3 );
printf ( " %d %d %d\n " , v [ 0 ], v [ 1 ], v [ 2 ]);
}
What does this print?
Forecast: Arrays feel like values — but do they copy?
v names 3 boxes in a row: 2,4,6. v used in an expression decays to &v[0].
Why this step? In C, passing an array does not copy all elements. The parameter int arr[] is secretly int *arr — a copied address of element 0.
triple(v,3) copies the address of v[0] into arr.
Why this step? This is Cell C in disguise: we copied an address, so arr[i] reaches the real boxes.
arr[i] = arr[i]*3 writes 6,12,18 into v's actual boxes.
Why this step? arr[i] is *(arr+i) — a dereference into caller memory.
Function returns; the address copy dies, the writes remain.
Answer: prints 6 12 18.
Verify: 2*3=6, 4*3=12, 6*3=18. The array was modified because only its address was copied. ✓ See Arrays as Function Arguments .
int safeDouble ( int * p ) {
if (p == NULL ) return 0 ; // degenerate: no box to read
* p = * p * 2 ;
return * p;
}
int main ( void ) {
int a = 0 ;
int r1 = safeDouble ( & a); // valid address, value is zero
int r2 = safeDouble ( NULL ); // degenerate: null pointer
printf ( " %d %d %d\n " , r1, r2, a);
}
What does this print?
Forecast: Two edge cases — a zero value (a=0) and a null address (NULL). Predict all three numbers.
First call: &a is a valid address holding value 0. p != NULL, so we run.
Why this step? 0 is a perfectly normal value — nothing special. *p = 0*2 = 0, and we return 0. So r1 = 0, and a stays 0.
Second call: NULL is the "points to nothing" address.
Why this step? Dereferencing NULL is undefined behaviour (a crash). The guard if (p == NULL) return 0; catches it and returns 0 without touching memory. r2 = 0.
a was written 0 in call 1, never touched in call 2.
Why this step? Both the zero-value and null-pointer degenerate cases are handled safely.
Answer: prints 0 0 0.
Verify: r1 = 0*2 = 0; guard makes r2 = 0; a = 0*2 = 0. All three zero, no crash. ✓
int addTax ( int price ) { return price + price / 10 ; } // +10%
int main ( void ) {
int p = 200 ;
addTax (p); // return value THROWN AWAY
int q = addTax (p); // return value STORED
printf ( " %d %d\n " , p, q);
}
What does this print?
Forecast: Does the first addTax(p) change p?
p = 200. addTax(p) copies 200 into price.
Why this step? Call by value again — price is a copy, p untouched regardless.
First call computes 200 + 20 = 220, then the value is discarded because it isn't assigned anywhere.
Why this step? A returned copy that nobody stores simply vanishes. p is still 200.
Second call computes 220 again and stores it in q.
Why this step? The caller sees the result only if it captures it. Same input, same output — functions of pure values are deterministic.
Answer: prints 200 220.
Verify: 200/10 = 20 (integer division), 200 + 20 = 220. p never modified (call by value), so p = 200, q = 220. ✓
A shop applies a flat $5 discount to a price, but never below $0. Write applyDiscount(int price) and predict outputs for prices \$12, \$5, and \$3.
int applyDiscount ( int price ) {
int out = price - 5 ;
if (out < 0 ) out = 0 ; // clamp at zero
return out;
}
int main ( void ) {
int p = 12 ;
printf ( " %d %d %d\n " ,
applyDiscount ( 12 ), applyDiscount ( 5 ), applyDiscount ( 3 ));
printf ( " %d\n " , p);
}
Forecast: Give four numbers.
applyDiscount(12): 12 - 5 = 7, 7 ≥ 0 so keep. Returns 7.
Why this step? Ordinary case — subtract and return the copy's result.
applyDiscount(5): 5 - 5 = 0, boundary case, 0 ≥ 0. Returns 0.
Why this step? Tests the exact clamp edge — result is exactly zero, not clamped.
applyDiscount(3): 3 - 5 = -2, negative → clamp to 0. Returns 0.
Why this step? The degenerate "would go below zero" case; the guard fires.
p is unrelated to the literal arguments; it stays 12.
Why this step? We passed literals, not p; and even passing p (a copy) wouldn't change it.
Answer: prints 7 0 0 then 12.
Verify: 12-5=7; 5-5=0; 3-5=-2→0. Money units in dollars, non-negative as required. ✓
The exam shows this and asks "what's wrong?":
int scale ( int x ); // prototype says: takes int
int main ( void ) {
printf ( " %d\n " , scale ( 3 ));
}
int scale ( double x ) { // definition says: takes double
return ( int )(x * 2 );
}
Forecast: Compiles? Runs? Crashes?
The prototype promises int scale(int). main prepares the argument 3 as an int.
Why this step? The compiler trusts the prototype when checking the call.
The definition delivers int scale(double) — a different function signature.
Why this step? Parameter type is part of the signature. int and double are not the same promise.
The compiler reports a conflicting types for scale error — the promise and the build disagree.
Why this step? This is the "keep prototype and definition identical" rule from the parent's mistakes list, made concrete.
Fix: make both int (or both double). With both int: scale(3) = 3*2 = 6.
Why this step? Once consistent, the call is well-defined.
Answer: As written → compile error . Fixed to int scale(int x){return x*2;} → prints 6.
Verify: With the corrected int version, 3 * 2 = 6. ✓
Recall Predict-the-output drill
int f(int x){x*=2; return x;} int a=4; int r=f(a); — values of r and a? ::: r = 8, a = 4 (copy changed, original safe).
Passing &a and doing *p = 9 inside — does a change? ::: Yes, to 9 (dereferenced the copied address).
Passing &a but doing p = &other then *p = 9 — does a change? ::: No — the pointer copy was redirected before the write.
Array v passed to f(int arr[]) and arr[0]=7 set inside — does v[0] change? ::: Yes — arrays decay to a copied address, so writes reach the real array.
addTax(p); on its own line with p=200 — new p? ::: Still 200; the returned copy was discarded.
Mnemonic The one question
For any call-by-value puzzle ask: "Did I copy a number, or an address?"
Number → original safe. Address (and I dereference it) → original changes.
Pointers in C — the tool that turns Cell A into Cell C.
Stack and Function Call Mechanism — where the copies live and die.
Arrays as Function Arguments — why arrays behave like Cell E.
Header Files and Separate Compilation — where prototype-vs-definition (Cell I) really bites.
Scope and Lifetime of Variables — why the callee's copies vanish on return.
Recursion — many frames, each with its own copies.