Functions, copied arguments and local state
What changes when a function is called
This program changes an x inside transform. Does the x in main change too? Predict the two printed numbers before reading the trace.
#include <stdio.h>
int transform(int x) {
x = x + 4;
return 2 * x;
}
int main(void) {
int x = 3;
int y = transform(x);
printf("%d %d\n", x, y);
return 0;
}A function packages a calculation under a name. In int transform(int x), the first int says that a call returns an integer. transform is the function’s name, and int x declares its integer parameter. The braces contain its body. The definition appears before main, so its name and parameter type are known when it is called.
In transform(x), the expression between parentheses is an argument. Here it reads the caller’s x. The parameter is the separate variable declared by the function; the argument supplies its starting value. The return value is the result sent back by return. These are three roles: supplied value, local receiving variable, and result of the call.
An ordinary int argument is passed by value: the parameter receives a copy of the argument’s value. Changing the copy does not change the caller’s integer variable. A repeated name does not join the two variables. Even if the parameter had been named p, the call would still supply 3.
Use two separate state records whenever control enters a function: one for the caller and one for the current call. While the helper runs, leave the caller’s record visible but unchanged unless a later caller statement assigns a new value.
Worked trace 1 A copied parameter
- Before the call, the caller is
mainand its state isx = 3. The initialization ofyis waiting for a value. - For
transform(x), read the caller’s currentx: the argument value is 3. Entertransformwith a separate parameter also namedx, initialized to 3. Record two boxes: callerx = 3; calleex = 3. - In
transform,x = x + 4reads the callee’s 3 and stores 7 in that same callee parameter. The caller’s box still contains 3. return 2 * xcomputes2 * 7 = 14and ends this call. It returns the number 14; it does not print 14 or assign to the caller’sx.- Back in
main, the call expression has value 14, so the waiting initialization completes withy = 14. The caller’s state is nowx = 3, y = 14. printfreads the caller’s two variables in the written output order. The output line is3 14.
The decisive check is where each assignment happens. x = x + 4 belongs to transform; int y = transform(x) belongs to main. The latter stores the returned number in y. Returning a value and assigning it to a chosen caller variable are separate steps. No “copy back” happens when the helper finishes.
return ends the current function call immediately. If an if branch executes a return, statements after that return are not executed during that call. The integer helpers here provide a return value on every reachable path. printf is the operation that displays output; the return in a helper only supplies the call’s value.
Worked trace 2 A loop inside a helper
The next helper adds the positive odd integers no larger than its argument. It is called twice. Predict whether the second call begins with the first call’s total.
#include <stdio.h>
int odd_sum(int n) {
int total = 0;
for (int k = 1; k <= n; k += 2) {
total += k;
}
return total;
}
int main(void) {
int n = 6;
int first = odd_sum(n);
int second = odd_sum(3);
printf("%d %d %d\n", n, first, second);
return 0;
}- Caller:
n = 6. The first call receives parametern = 6and initializes its owntotal = 0. - First call, test
k = 1:1 <= 6is true. Add 1, sototal = 1; thenk += 2makesk = 3. - First call, test
k = 3: true. Add 3, sototal = 4; update tok = 5. - First call, test
k = 5: true. Add 5, sototal = 9; update tok = 7. - First call, test
k = 7:7 <= 6is false. The body has run 3 times and the test 4 times. Return 9; caller storesfirst = 9. - Second call: the argument is the constant 3. A new parameter has
n = 3, and this call’s new localtotalis initialized to 0. Callernremains 6. - Second call, test
k = 1: true. Add 1, givingtotal = 1; update tok = 3. - Second call, test
k = 3: true. Add 3, givingtotal = 4; update tok = 5. - Second call, test
k = 5: false. The body has run 2 times and the test 3 times. Return 4; caller storessecond = 4. - Caller state:
n = 6, first = 9, second = 4. The output line is6 9 4.
At the start of each loop test, total contains the sum of the odd positive integers already processed. Initially none have been processed, so 0 is correct. Each successful body adds the current odd k; the update moves to the next odd integer. This explains the accumulator’s meaning, not just the final number.
For the supplied inputs, k increases by 2 until it exceeds n, so the loop terminates. At the boundary odd_sum(0), the first test is 1 <= 0, which is false: no body executes and the function returns 0. A helper with a loop can still return normally when its loop executes zero times.
The local variable total is initialized by int total = 0 every time this declaration is reached on a new call. “Local” does not mean “automatically zero”: the initializer is what supplies 0. The second call has its own initialized local; it does not continue from 9. Here total can be named from its declaration through the rest of the helper’s body, including the loop body. The k declared in the for initializer can be named in that loop’s test, update and body; it is outside its scope after the loop. main cannot name either helper-local variable. It receives a number through the return value instead.
The caller’s n and the helper’s parameter n are also separate. The second call uses the literal 3, so it does not even read caller n for that argument. The first call’s returned 9 remains in caller first while the second call computes its result.
Combining small functions
One function’s returned value can be another function’s argument. Read outer(inner(value)) by finding the inner call’s result, then using that number as the argument of the outer call. This follows the data needed by the calls; it is not a rule that unrelated C arguments always run left to right.
In the practice below, twice multiplies its integer parameter by 2 and plus3 adds 3. Applying the two calculations in a different order can change the result. Keep each call’s argument and returned value visible instead of mentally merging the two function bodies. These helpers use only their integer parameters and local calculations, so there are no other state changes to track.
A reliable call trace
- Read the current value of the argument expression in the caller.
- Start a separate callee state and give the parameter that value.
- Execute the helper’s statements in order, including tests, loop updates and local initializers.
- Stop that call at the executed
returnand record its value. - Resume the caller’s pending expression and store the value only where the caller’s statement says to store it.
All programs here use C11, initialized ordinary signed int values and small, representable intermediate results. The shown helpers are defined before their calls. Trace only the supplied inputs and the explicitly stated small boundaries; a loop formula is not a promise of safe arithmetic for every possible int.
Practice
For each program, record the caller state, the argument received by each helper, and the returned value. Try all four before opening their solutions.
Practice 1
Give the caller value and returned result.
#include <stdio.h>
int decrease(int p){p-=2;return p;}
int main(void) {
int z=9;
int ans=decrease(z);
printf("%d %d\n",z,ans);
return 0;
}Practice 2
Compare the two compositions.
#include <stdio.h>
int twice(int p){return p*2;}
int plus3(int p){return p+3;}
int main(void) {
int a=twice(plus3(4));
int b=plus3(twice(4));
printf("%d %d\n",a,b);
return 0;
}Practice 3
Find results at a negative input, zero, and a positive input.
#include <stdio.h>
int nonnegative(int p){if(p>=0){return 1;}return 0;}
int main(void) {
int a=nonnegative(-2);
int b=nonnegative(0);
int c=nonnegative(3);
printf("%d %d %d\n",a,b,c);
return 0;
}Practice 4
Trace two calls and explain why the second total starts at zero.
#include <stdio.h>
int sum_to(int n){int total=0;for(int k=1;k<=n;k++){total+=k;}return total;}
int main(void) {
int a=sum_to(4);
int b=sum_to(2);
printf("%d %d\n",a,b);
return 0;
}Practice solutions
Practice 1
- Caller starts with
z = 9. The calldecrease(z)supplies argument value 9. - Inside
decrease, the separate parameter starts atp = 9.p -= 2givesp = 7. return preturns 7. Caller storesans = 7; callerzis still 9.- The output line is
9 7.
7 7 would incorrectly copy the parameter’s change back to z. 9 9 would ignore the executed subtraction and returned value. 7 9 makes both mistakes. Track the variable that each assignment actually names.
Practice 2
- For
a, the inner call isplus3(4). Its parameter is 4 and it returns4 + 3 = 7. - The outer call is therefore
twice(7). It returns7 * 2 = 14, stored ina. - For
b, the inner call istwice(4), which returns 8. - The outer call is therefore
plus3(8), which returns 11, stored inb. - The output line is
14 11. Each helper uses a new parameter for its own call.
11 14 reverses which composition belongs to a and b. 14 14 assumes adding 3 and multiplying by 2 can be interchanged; they cannot, because in the first composition the added 3 is also doubled. 7 8 stops after the inner calls and omits the outer calls.
Practice 3
- With argument -2, parameter
p = -2. The testp >= 0is false, so execution reachesreturn 0. Thusa = 0. - With argument 0, the test
0 >= 0is true.return 1ends this call immediately, so the laterreturn 0is not executed. Thusb = 1. - With argument 3, the test is true and the call returns 1. Thus
c = 1. - The output line is
0 1 1. Both routes return an integer.
0 0 1 treats >= as > and wrongly excludes zero. 0 0 0 assumes the final return always overwrites an earlier return, but a returned call has already ended. -2 0 3 repeats the arguments even though the function returns a classification value.
Practice 4
- First call: parameter
n = 4, localtotal = 0. - For
k = 1, 2, 3, 4, the successive totals are1, 3, 6, 10. After each body,kincreases by 1. Atk = 5, the test fails. There are 4 body executions and 5 tests. - Return 10 and store
a = 10in the caller. - Second call: parameter
n = 2, a fresh localtotal = 0. Fork = 1, 2, totals are1, 3; atk = 3, the test fails. There are 2 body executions and 3 tests. - Return 3 and store
b = 3. The output line is10 3.
10 13 incorrectly starts the second accumulator at 10. 6 1 omits the endpoint despite k <= n. 15 6 wrongly executes the body for the first failed-test values 5 and 3. The update prepares the next test; it does not guarantee another body execution.
Check your explanation
A complete explanation distinguishes the caller’s variable, the current call’s parameter, and the returned number. If a helper has a loop, include its first failed test. Use these same records in the separate C foundation learning review.
Source note
C11 semantics are referenced to the WG14 N1570 committee draft. For copied arguments, local initialization, loop scope and return values, see §§6.5.2.2, 6.2.1–6.2.4, 6.8.5.3 and 6.8.6.4.
Analogy
Think of a helper as a calculation desk. You write the number 3 on a request slip while keeping your own record of 3. The desk writes 3 in its own working box, changes that box to 7, and sends back a result slip showing 14. Your record still says 3. If you file the result under y, then y becomes 14.
On the next visit, the desk begins a new worksheet. In odd_sum, the written instruction int total = 0 starts that worksheet’s total at zero. An old worksheet’s total is not reused. The separate boxes model distinct integer variables; the paper story is only a tracing aid, not a claim about how a computer physically stores them.
Quick reference
- Function definition: return type, function name, typed parameters, then a braced body; place these definitions before calls.
- Argument: the value supplied by the caller. Parameter: a separate receiving variable. Return value: the result of the call expression.
- An ordinary
intparameter receives a copy. Assigning to it does not assign to the caller’s variable. return expression;evaluates the expression, ends this call and supplies its result. It does not print.- A caller stores the returned number only when its own statement does so.
- When a local declaration is reached in a new call, its initializer runs for that call.
int total = 0supplies zero; locality alone does not. - A variable declared in a block is usable only within its scope. The
int kin aforinitializer belongs to that loop, not the statements after it. - For a nested single-argument call, compute the inner return value to obtain the outer argument. Do not infer a general left-to-right argument-order rule.
- Trace loops inside helpers exactly as before: test, body, update, next test, including the final false test.
- Worked trace 1 prints
3 14; Worked trace 2 prints6 9 4. Restrict arithmetic claims to the stated small inputs.
Notes for this lesson
Sign in to keep your progress. Sign in