Breakout, the game the course builds in HiSoft C, running on the Tatung Einstein: coloured brick rows, the bat and the ball on a black court
← Back to Courses
module
13

Functions

Introduction

So far every program has been one function, main, and it has been getting long. A function is a piece of program with a name, which you call from anywhere by writing the name and, in brackets, what it needs to know. Breakout will have a dozen of them - one to move the ball, one to move the bat, one to take out a brick - and the game loop will read like a list of what happens each tick. This section is how to write one, and this compiler has an older way of saying what the arguments are than you may have seen.

Writing One

square(n) int n;
{
    return n * n;
}

Four parts. The name, square. In brackets, the names of its arguments - what the caller passes in. Then, after the brackets and before the {, a declaration of each argument's type, ending in a semicolon, exactly as a variable would be declared. Then the body in braces. That is the shape this compiler wants, and the only shape it takes: write square(int n) with the type inside the brackets, and it says ERROR 56 ... bad formal parameter list. Types go outside.

Inside the body, n is a variable like any other, holding whatever the caller passed. return ends the function and hands a value back, so that square(5) is 25 wherever it is written: in a printf, in a sum, as an argument to another function.

A function's value is an int unless you say otherwise, and main is just a function that happens to be the one the program starts in. Two arguments are declared together, as variables are:

bigger(a, b) int a, b;
{
    if (a > b)
        return a;
    return b;
}

A return may come anywhere, and the function ends there. If the body reaches its } with no return, the caller gets whatever the function happened to leave behind - a number, but not one that means anything. Either return a value from every path, or do not use the value.

Functions That Give Nothing Back

greet below prints something and returns nothing worth having. You may write it with no type, as main is written, or as void greet(c). The word void is not part of this compiler: it is a #define in STDIO.H that turns it into int, so it works only because STDIO.H is at the top of the file. It says to the reader that the value is not meant to be used, and the compiler does not care either way. The course writes void where a function is only called for what it does.

Arguments Are Copies

The function gets the value of each argument, in a variable of its own. Change that variable and the caller's is untouched:

twice(x) int x;
{
    x = x * 2;
    return x;
}

twice(v) returns 10 for v of 5, and v is still 5 afterwards. That is the normal way of things, and it is what makes functions safe to call: they cannot alter your variables. When you want them to - move(ball) had better move the ball - S16 has the answer, which is to hand the function the address of the thing instead of a copy of it.

A variable declared inside a function exists only while that call runs, and starts each call with no value (S10). Mark it static and it lives for the whole program, keeping its value from one call to the next - static int n = 0; n++; return n; returns 1, then 2, then 3.

A Function That Calls Itself

fact(n) int n;
{
    if (n <= 1)
        return 1;
    return n * fact(n - 1);
}

fact(5) is 5 times fact(4), which is 4 times fact(3), and so on down to fact(1), which is 1. Each call has its own n. Nothing about the compiler needs to be told that a function may call itself; it is just a call. fact(5) is 120 and fact(7) is 5040. fact(8) is -25216, because 40320 does not fit in an int (S9) - the recursion is fine; the arithmetic is not. S21 says how deep it can go.

Where A Function Goes

Functions go one after another in the file, in any order, and main may be first or last. One rule matters: the compiler reads the file from the top, and when it meets a call to a function it has not seen yet, it assumes the function returns an int. For square that is right, so square may be defined below main. For a function that returns something else - S17's functions that return a string, char *name() - the assumption is wrong, and when the compiler reaches the real definition it says ERROR 20 ... duplicate declaration - type mismatch at that line. So: a function that returns an int may go anywhere; any other goes above its first call - or is declared above it, with the word extern: extern char *name(); tells the compiler the type without defining the function. That is how STDIO.H lets you call strcpy (S17), which returns a char *, before the library at the end of the file has been read: it has a line of exactly those declarations. The course puts every function of its own above main and avoids the question.

The Code

#include STDIO.H

square(n) int n;
{
    return n * n;
}

bigger(a, b) int a, b;
{
    if (a > b)
        return a;
    return b;
}

greet(c) char c;
{
    printf("Hello, %c\n", c);
}

fact(n) int n;
{
    if (n <= 1)
        return 1;
    return n * fact(n - 1);
}

main()
{
    int i;
    greet('G');
    for (i = 1; i <= 5; i++)
        printf("%d squared is %d\n", i, square(i));
    printf("bigger of 3 and 8 is %d\n", bigger(3, 8));
    printf("5 factorial is %d\n", fact(5));
    printf("7 factorial is %d\n", fact(7));
    printf("8 factorial is %d\n", fact(8));
}

#include ?STDIO.LIB?

Starting from

Typed as FUNC.C; compiled and run.

What you should see

Hello, G
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25
bigger of 3 and 8 is 8
5 factorial is 120
7 factorial is 5040
8 factorial is -25216

The Call That Breaks The Machine

Save your work before you try this one.

The compiler checks nothing about a call except that the name exists. It does not count the arguments. Call bigger(3) - one argument for a function that declared two - and it compiles without a word. When it runs, the machine stops making sense. In three runs of a program that did this it printed its first line and then two characters of rubbish and came back to the prompt; printed nothing and never came back; and dropped into the Einstein's monitor with a register dump (S7). Which of the three you get is luck.

The reason is worth knowing, because it tells you what to look for. The caller puts the arguments where the function will look for them; the function takes what it was declared to take. Pass one and it reads two, and the second is whatever was lying there - and from then on nothing is where either side thinks it is. So when a program that compiled cleanly does something senseless the moment a function is called, count the arguments at every call of it before you look anywhere else. There is no message, and there never will be.

A Name You Cannot Use

One more thing that will happen to someone. The compiler's runtime has functions of its own with ordinary names, and if you write a function with one of those names it says ERROR 21 ... duplicate declaration - storage class mismatch at your function's first line. swap is one - S16 wants to write it and calls it exchange instead. If a function you have just written gets that message and you have not declared it twice, rename it.

Change One Thing

  • Move square to after main, at the end before the #include. Does it still compile and run? Then do the same to greet, changing its first line to char *greet(c) char c;. What does the compiler say now, and at which line?
  • Change square(n) int n; to square(int n). What is the message?
  • Save, then change bigger(3, 8) to bigger(3). Compile, run, and restart the Einstein afterwards.

Exercises

13.1 Write cube(n) and print the cubes of 1 to 5. What is the last one you can print correctly, and why?

13.2 Write smallest(a, b, c) returning the smallest of three, using bigger as a model, and test it with the arguments in every order.

13.3 Write power(base, n) that returns base to the power n by calling itself, and print the powers of 3 from 3 to the power 1 upward until one is wrong.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
bad formal parameter listA type inside the brackets. Declare the arguments after the brackets, before the {.
duplicate declaration - type mismatch at a function's first lineIt returns something other than int and is called above its definition. Move it above the call.
duplicate declaration - storage class mismatch at a function's first lineIts name belongs to the runtime. Rename it.
bad declaration at voidNo #include STDIO.H at the top.
A function's value is a number that means nothingIt reached } without a return.
A program that compiled cleanly goes senseless at a callThe call has the wrong number of arguments. Count them.
A variable the function changed has not changedIt changed its own copy. S16.

Summary

name(args) type args; { body } - types after the brackets. return hands back a value, int unless declared otherwise; void from STDIO.H says there is none. Arguments are copies; static locals persist. A function may call itself. Functions returning int may go anywhere; others go above their first call. The argument count is never checked - a wrong one breaks the machine.

Next

S14, Asking - reading what the player types: a whole line, a number, and a single key without waiting for ENTER.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.