We’ve covered a lot of ground already, but so far we haven’t seen anything about MSX-C that couldn’t be done with some effort with MSX-BASIC. That changes with the posts that follow.
Today we’re going to see one of the strengths of the C programming language: functions.
Let’s get to it.
Defining and using functions
When we write a program, as the source code becomes bigger and bigger it becomes much more complex and more difficult to maintain. Because of this, no matter what programming language we use, we normally divide a big program into smaller parts that perform a specific action. For example, in BASIC we used to write subroutines and call them with GOSUB.
In C we do this with functions.
Defining a function
Before we can use a function we have to indicate what code it is going to execute and what name we’re going to give to it. We call this the function definition.
The way we define a function changes slightly depending on whether it takes parameters or not, and whether it returns any data or not. Let’s start with the simplest cases and then look at more complex scenarios.
Function without parameters nor return value
The simplest case is a function that doesn’t take any parameters and that performs some action, but doesn’t return any data. It’s basically just a bunch of C statements grouped together and given a name. This is how we define such a function:
For example, the program below defines two very simple functions, topbot() and sides(), that we can use to print a box on the screen. topbot() prints a horizontal line that will form the top and bottom sides of the box. The sides() funtion prints the left and right borders:
If you look at the main() function you’ll see that it calls topbot() once, followed by sides() four times, then topbot() again once. The result is a text rectangle on the screen:
By defining these two functions we can execute the code inside them by just invoking the function anywhere in the program. In this particular case this isn’t a huge benefit because each function contains just two lines of code, but in more complex situations functions will dramatically increase both the readability of the program and make it easier to develop.
Generally (with a few exceptions that we’ll discuss in the future) we’ll write a function for each block of code that we need to execute at least twice in different parts of our programs.
Now is a good time to say a bit more about the library functions. All these functions we’ve been used so far (printf(), puts(), putchar(), getch(), setbuf(), etc) are part of the C Standard Library (do not confuse with the MSX-C Library package that we’ll review in a series of future posts). These functions don’t have any special status from the compiler’s point of view and are no more important than any function we define ourselves. The only differences are that these are distributed with the compiler because they’re a standard: almost every C compiler for any platform will include most of them. However, they’re just functions like any other, and we can change them if we want to. MSX-C even includes their source code files, in case we want to rewrite them (which we will most likely never need to do, but it’s nice to know that we can). What this means is that any function we write becomes as important as any of the functions included by default with the compiler.
The two functions we’ve written so far aren’t so different from subroutines in BASIC: we call them, they perform an action, and they return. However, one very important difference with MSX-BASIC is that instead of using meaningless line numbers we use a function name that explains what the function does. Because of this, C programs are generally easier to follow than BASIC programs.
Where to define a function
We can call any function from inside any other function, even the ones we’ve defined. However, as we saw a few months ago, the compiler needs to know that a function exists before we can use it in our program. Trying to use a function that hasn’t been defined yet results in a compiler error.
As an example, the following program calls the hello() function before defining it, and because of this the compile process fails.:
One possible solution to this problem is to move the definition of hello() to the beginning of the program, so it already exists when main() calls it:
However, there are cases when changing the order isn’t possible (for example, two functions that call each other, or functions declared in different source files). In these situations we have to declare the functions before we use them. This means that we’re telling the compiler not to raise an error, because we’ll define the functions somewhere else.
In chapter 18 we saw an example of this near the end of the post. We declared several functions before defining them by entering their names just after the #include <stdio.h> line:
Back them we used an abbreviated form of the declarations, but the correct way is to use the extern keyword to tell the compiler that we’re defining the function somewhere else.
Let’s illustrate this by extending the first program from this post:
The program starts with the declaration of the square() function. We’re telling the compiler not to issue an error because this function will exist at some point in the future.
Next we define main() and call square() twice from within it. This prints two squares on the screen and finishes the program.
Next we define square(). In the first two lines of square() we declare the functions topbot() and sides() as external. As before, we’re telling the compiler not to complain because these functions will exist later at some point. We call topbot(), sides() twice and topbot() again to create the shape of a square on the screen.
Finally, the program ends with the definitions of topbot() and sides().
Notice that we declared topbot() and sides() inside the square() function instead of declaring them at the beginning of the program. By doing this we’re making these two functions available only inside square(), and nowehere else in the program. Effectively, we’re making these two functions private to square().
Looking at it in another way: main() doesn’t need to know about topbot() or sides() because it only cares about calling square(). This makes the program easier to understand and maintain: if we need to remove the square() function in the future then we know that we can remove topbot() and sides() as well, because only square() used them.
If you copied the program correctly it should compile without errors and print two squares on the screen when you run it:
In this post we’ve learnt how functions help make our programs easier to develop by dividing a large program into smaller, more manageable parts. We’ve seen how to define simple functions that don’t take any parameters and how to declare functions in order to be able to use them in the program before they’ve been defined. We’ve also seen that we can put the function declaration inside the function that calls it, effectively making the called function private to the function that’s declaring it.
In the next post
Next time we’ll learn about local variables, which can be seen only by the function that defines them. We’ll also see how to define functions that accept parameters.
This series of articles is supported by your donations. If you’re willing and able to donate, please visit the link below to register a small pledge. Every little amount helps.