Computer ScienceCore20 min read

Functions in C

Breaking a program into pieces, and what a function can and cannot change

This topic appears in:

01

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.

int square(int n) {/* return type, name, parameters */return n * n;/* return sends a value back */}int main() {int r = square(6);/* the call — 6 is the argument */printf("%d", r);/* prints 36 */}a function returning nothing is declared void, and a return with no value simply exits it

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.

02

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.

TermMeaning
Parameterthe variable named in the function definition
Argumentthe actual value passed in at the call
Return typethe type of value sent back, or void for none
Prototypea declaration with no body, placed before main
Local variabledeclared inside a function; exists only while it runs
Global variabledeclared outside all functions; visible everywhere
03

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.

Worked example

Explain why this prints 5, not 10: void doubleIt(int x) { x = x * 2; } int main() { int a = 5; doubleIt(a); printf("%d", a); }

  1. The call doubleIt(a) copies the value of a into the parameter x.x and a are two distinct variables that happen to hold the same value.
  2. Inside the function, x = x * 2 changes x to 10.The assignment is real, but it only affects the local copy.
  3. When the function ends, x ceases to exist and its value is discarded.A local variable lives only for the duration of the call.
  4. a was never touched, so it still holds 5.Nothing in the function ever referred to a — only to its copy.
  5. To change a, pass its address: void doubleIt(int *x) { *x = *x * 2; } called as doubleIt(&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.

04

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

  1. Functions give reuse, testability and division of work.
  2. A prototype above main lets a function be called before it is defined.
  3. Parameter = named in the definition; argument = the value passed at the call.
  4. C passes by value, so a function cannot change the caller's variable — unless given its address.
  5. Local variables exist only during the call; globals are visible everywhere and are best avoided.
05

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 troubleWhat it usually means
The name contains "and"it is doing two things and should be two functions
It reads or writes a globalits behaviour cannot be predicted from the call
It needs eight parametersthe data probably belongs together in a structure
It is 200 lines longthere are smaller functions hidden inside it
You cannot test it aloneit 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.

Practice questions

6 questions · 20 marks · full working on every one

Try each one on paper first, then open the working. The marks are shown where they are actually awarded, because that is where they are actually lost.

Short questions

3 · 6 marks

Two marks each, in the style of the short-question section of the paper. Answer in two or three lines.

SQ1[2 marks]
State two advantages of using functions.
Model answer

Reusability — code written once can be called many times, so a correction is made in one place. Testability and readability — each function can be understood and tested on its own, and a long program becomes a set of short comprehensible pieces.

Examiner tip. Two distinct advantages. "It makes the program shorter" and "it avoids repetition" are the same point twice.

SQ2[2 marks]
Differentiate between a parameter and an argument.
Model answer

A parameter is the variable named in the function definition, which receives a value. An argument is the actual value supplied at the point of call. In square(6) calling int square(int n), n is the parameter and 6 is the argument.

Examiner tip. The example naming both makes the answer unambiguous and takes one line.

SQ3[2 marks]
Why is a function prototype needed?
Model answer

C processes a file from top to bottom, so a function called before its definition appears has not yet been seen by the compiler. A prototype placed above main declares the name, return type and parameter types in advance, allowing the call to be checked.

Examiner tip. The top-to-bottom reading is the reason. Without it the answer describes what a prototype is but not why it exists.

Solved numericals

2 · 8 marks

Full working, one step per line, with the marks shown where they are awarded.

N1[4 marks]
Write a C function that takes two integers and returns the larger, and show how it is called.
Full working
  1. int larger(int a, int b) { — correct return type and two int parameters[1]
  2. if (a > b) return a;[1]
  3. else return b; }every path must return a value[1]
  4. Called as int max = larger(7, 3); with the returned value stored or usedcalling it without using the result wastes it[1]

int larger(int a, int b) with an if-else returning a or b, called as larger(7, 3).

Examiner tip. A function with a non-void return type must return a value on every possible path. Falling off the end without a return is undefined behaviour.

N2[4 marks]
Explain why a C function cannot normally change a variable belonging to the caller, and how it can be made to.
Full working
  1. C passes arguments by value — the function receives a copy of the argument[1]
  2. The parameter is a separate variable, so assigning to it changes only the copy, which is discarded when the function ends[1]
  3. To change the original, pass its address using &, and declare the parameter as a pointer[1]
  4. The function then writes through that address with *, reaching the caller's variable — which is exactly what scanf doesthe scanf connection earns the mark[1]

Pass by value means the function gets a copy. Pass the address instead and write through the pointer.

Examiner tip. Linking it to scanf is worth doing: it explains the ampersand students have been typing since chapter 10 without knowing why.

Long questions

1 · 6 marks

Theory and numerical together, as they appear in the long-question section.

LQ1[6 marks]
A program calculates the area of several circles.
  1. Write a function that takes a radius and returns the area, using a constant for π.
  2. Explain the difference between a local and a global variable, and which the function should use.
  3. Explain what would happen if the function were called before being declared or defined.
Mark scheme
  1. #define PI 3.14159 or const float PI = 3.14159;[1]
  2. float area(float r) { return PI * r * r; }float return type and parameter[1]
  3. A local variable is declared inside a function and exists only while it runs; a global is declared outside all functions and is visible everywhere[1]
  4. The radius should be a parameter and any working values local, so the function depends only on what it is given and cannot be affected by the rest of the program[1]
  5. C reads the file top to bottom, so calling it earlier with no prototype means the compiler has not seen the declaration[1]
  6. The compiler reports an error, or in older C assumes a default return type of int and produces a wrong result silentlythe silent-failure case is the sharper point[1]

(a) float area(float r) returning PI * r * r (b) local for working values; parameters for input (c) an error, or in older C a silent wrong result from an assumed int return

Examiner tip. The silent failure in older C is worth knowing: the compiler assumed an undeclared function returned int, so a function returning float produced nonsense with no warning at all.