Why functions exist
A function is a named block of code that performs one task and can be called from anywhere. Three benefits follow, and the exam asks for them.
Code is written once and used many times, so a correction is made in one place. A long program becomes a set of short pieces, each of which can be understood and tested on its own. And a large program can be divided among several people, each responsible for particular functions.
Step through Function call. Execution begins at main, jumps into the function, and comes back with a value. Notice n is a separate variable holding a copy of what a contained.
Declaration, definition and call
Three separate things, which the exam distinguishes.
A declaration — also called a prototype — tells the compiler the function's name, return type and parameter types, without providing the body: int square(int n);. A definition supplies the body. A call is where it is used.
The prototype exists because C reads a file from top to bottom. Calling a function defined further down the file, with no prototype, means the compiler has not yet seen it. Prototypes are therefore placed above main, and the definitions may go anywhere below.
| Term | Meaning |
|---|---|
| Parameter | the variable named in the function definition |
| Argument | the actual value passed in at the call |
| Return type | the type of value sent back, or void for none |
| Prototype | a declaration with no body, placed before main |
| Local variable | declared inside a function; exists only while it runs |
| Global variable | declared outside all functions; visible everywhere |
Pass by value, and what it means
C passes arguments by value. The function receives a copy, so changing a parameter inside the function has no effect on the caller's variable. This is the single most examined idea in the chapter.
A classic demonstration: a swap function that exchanges its two parameters appears to work inside the function and changes nothing outside it, because it swapped two copies. To modify a caller's variable you must pass its address — a pointer — which is exactly what scanf requires the ampersand for.
Explain why this prints 5, not 10: void doubleIt(int x) { x = x * 2; } int main() { int a = 5; doubleIt(a); printf("%d", a); }
- The call
doubleIt(a)copies the value ofainto the parameterx.x and a are two distinct variables that happen to hold the same value. - Inside the function,
x = x * 2changes x to 10.The assignment is real, but it only affects the local copy. - When the function ends, x ceases to exist and its value is discarded.A local variable lives only for the duration of the call.
awas never touched, so it still holds 5.Nothing in the function ever referred to a — only to its copy.- To change a, pass its address:
void doubleIt(int *x) { *x = *x * 2; }called asdoubleIt(&a);Now the function has a route back to the original variable, which is precisely what the ampersand in scanf provides.
The function modified a copy. Pass the address with & to modify the original.
Scope and lifetime
A local variable is declared inside a function and exists only while that function is running. Two functions may each have a variable called i and they are entirely unrelated, which is what makes functions independent of one another.
A global variable is declared outside every function and is visible to all of them. Globals look convenient and are best avoided: any function can change one, so tracing where a wrong value came from means examining the whole program rather than one function. Passing values as parameters and returning results keeps each function's effects visible in its own signature.
Recursion
A function may call itself. Every recursive function needs a base case that returns without recursing, or the calls continue until memory is exhausted — a stack overflow. Factorial is the standard example: if (n <= 1) return 1; else return n * factorial(n - 1);. The base case is the first line, and omitting it is the only way to get this wrong.
Before you leave this chapter
- Functions give reuse, testability and division of work.
- A prototype above main lets a function be called before it is defined.
- Parameter = named in the definition; argument = the value passed at the call.
- C passes by value, so a function cannot change the caller's variable — unless given its address.
- Local variables exist only during the call; globals are visible everywhere and are best avoided.
Designing a good function
Splitting a program into functions is not automatically an improvement — badly chosen functions are harder to follow than the code they replaced. Three principles decide it.
A function should do one thing, and its name should say what. calculateAverage is a function; doStuff is a warning. It should depend only on its parameters, not on global variables, so that reading its signature tells you everything it can be affected by. And it should be short enough to see at once — if it will not fit on a screen, it is probably doing more than one thing.
| Sign of trouble | What it usually means |
|---|---|
| The name contains "and" | it is doing two things and should be two functions |
| It reads or writes a global | its behaviour cannot be predicted from the call |
| It needs eight parameters | the data probably belongs together in a structure |
| It is 200 lines long | there are smaller functions hidden inside it |
| You cannot test it alone | it depends on something it was not given |
The test that settles it
Ask whether the function could be tested on its own, by calling it with known values and checking the result. If it can, it is well designed — inputs in, output out, nothing hidden. If testing it requires setting up global variables or opening a file first, it is doing more than computing something, and the extra work is what should be separated out.