What's a function anyway?




Document: Software Engineering 1: Course Notes

next Moving Data Around
up Chapter 2 pp. 20-26
previous Chapter 2 pp. 20-26

What's a function anyway?

In C a function is a unit of code that can be used, or called, or invoked, from some other place in the program. Better names for this idea, used in other languages, are "subprogram", "subroutine", or "procedure". But, for reasons which, no doubt, seemed sensible at the time, the designers of C plumped for the word "function" and I am afraid we are stuck with it!

Functions are handy because they allow you to break a program down into manageable chunks, where each chunk is quite small (typically 10-20 lines) and does just one or two definite things. Thus each chunk is pretty readable or understandable in its own right. If a single function starts getting too complicated, then we chop some coherent piece of it out and wrap it up in a function of its own, which now is merely "called" by the original function.

Functions are also handy because they support "re-usability" in a program. Thus, if there is a particular generic kind of manipulation which has to be done in two or three, or 20 different situations in a program, we can write a function to do this manipulation; then each time we need that manipulation, we call the function, instead of repeating all the detailed code.

In fact, we have been using functions all along already, without really noticing it. The block of code in our programs with the following outline:

    int main(void)
    {
    .
    .
    .
    }

is technically the definition of a function whose name is main().gif

All C functions have names, just like variables. When you are defining your own functions, you get to pick the names, again just as with variables.

But the function name main() is special in this regard. Every C program is required to have a function called main(). Normally functions get invoked or executed by some other function calling them, or transferring control to them. But if this were true of all functions then we would have an infinite regress, with no way for any function to get started in the first place. What is special about the function called main() is that it does not have to be called or started up by any prior function - it is started up automatically as the first function to be executed when the whole program is started executing. In fact, it will normally cause very bad things to happen if main() is actually called by any other function...

OK, so, in a sort of a way we have already seen what a function definition looks like, in the form of the definition of the main() function. We have also seen what function calls or invocations look like, because we have used some of the so-called standard library functions, such as printf(), scanf(), sin(), cos(), and so on. So calling a function basically involves just putting its name into a statement (or, more precisely, into an expression). When (or if) the statement gets executed, then that will cause the function to be invoked, and control will pass, temporarily, into the called function; that function will do its thing, and then control will pass back to where the function was called (the so-called "calling site").

Thus, consider this simple program:

    #include <stdio.h>

    int main(void)
    {
      printf("Help: I'm trapped in the computer!!!\n");

      return(0);
    }

When the program is started, execution will start at the main() function. This is why a function called main() must be present: otherwise the computer has no way of knowing where in our program execution should start. This may not seem like much of a problem at the moment: after all, our program only has one function, so that is "obviously" where execution should start - regardless of what the function is called. But very soon we will see programs where the program consists of more than one function and some ambiguity would then necessarily arise. Even still you might think the computer could simply start execution at the first function that is defined (or the last for that matter) rather that looking for a function of a particular name. Indeed, with many other programming languages that is the approach adopted. But, again for reasons best known to the original C language designers, they decided to take the approach of insisting that a function with the specific name main() must always be present, and that that is where execution will start...

Anyway: in the example above execution starts at the function main(). The very first statement of this function is then a call, or invocation, of the standard library function called printf(). So execution is transferred to that function. In the meantime, the main() function is essentially put into suspended animation. So printf() is executed, which, in this case, means that the string "Help: I'm trapped in the computer!!!\n" will be printed out on the screen. Once printf() has done that, it "terminates", or, more technically, executes a return statement, which returns control to the calling site - in this case, the function main(). So now, main() becomes re-animated, and continues execution with its next statement. This is a return statement which causes main() itself to terminate. Now, as just explained, when a function executes a return this normally causes control to return to wherever the function was called from; but this is a bit tricky with main() because, technically, main() was not called from anywhere (or not, at least, from any other function). In practice, when the main() function does a return then the whole program is terminated, and control is returned to the "external environment" - in our case the Turbo-C++ IDE. This makes a sort of sense since main() was the first, or "top-level" function to be invoked, and in this contrived way, its "calling site" simply is the "external environment"...

So: be sure you have the basic idea of a function as a unit or chunk of code that can be "invoked". With this facility at our disposal, a complete program need no longer consist of just one function, called main() (together with whatever standard library functions main() calls). Instead, once main() starts to get a bit long or complicated, we break down the work that main() is doing, and the statements that constitute it, into some set of more or less coherent sub-blocks. Each of these is then hived off into a "function" of its own. We will give each such function some name; this should be as meaningfull as possible, but we have essentially complete freedom in this (as we have in naming variables). Whereas the name of the top-level function is constrained to be precisely main(), the functions called by main() can have any names we like. These additional functions which we name and write are called user-defined functions to distinguish them from standard library functions.

In the simplest case then, the outline of a C program now looks something like this:

    void function1(void)
    {
      /* Statements making up function1() appear
         here... */
         .
         .
         .

      return;
    }

    void function2(void)
    {
      /* Statements making up function2() appear
         here... */
         .
         .
         .

      return;
    }

    void function3(void)
    {
      /* Statements making up function3() appear
         here... */
         .
         .
         .

      return;
    }

    int main(void)
    {
      function1();
      function2();
      function3();

      return 0;
    }

This program consists of the definitions of four functions called (rather unimaginatively!) function1(), function2(), function3() and main(). When the program is started up, execution will begin at main() (even though main() is actually the last function to be defined!); execution immediately transfers to function1(), and main() is suspended; when control is return'ed from function1() to main(), then main() goes on to call function2() and is again suspended while function2 is executed; similarly, when function2() executes its return statement control comes back to main(), and then function3() is invoked; finally, function3() executes its return, control comes back to main(), and main() itself executes a return, causing the complete program to terminate, and control comes back to the IDE.

Be clear that you understand how this function call/return mechanism allows main() to be broken down into smaller, hopefully simpler, chunks. In this, admittedly contrived, example, the operation of main() itself has actually become trivial - it just calls the functions function1(), function2() and function3() in sequence. All the complicated stuff that might have been in main() has been split up, and moved into the "sub-" functions.

Of course, this process of breaking things down can be repeated indefinitely. Thus, in the example above, if the function function1() turned out to be still too complicated it might be broken down into smaller pieces still:

    void function1a(void)
    {
      /* Statements making up function1a() appear
         here... */
         .
         .
         .

      return;
    }

    void function1b(void)
    {
      /* Statements making up function1b() appear
         here... */
         .
         .
         .

      return;
    }


    void function1(void)
    {
      function1a();
      function1b();

      return;
    }

And, of course, it might turn out that any of these functions can conveniently be used (or called) multiple times, possibly from multiple other functions. In the outline above, it might turn out that function1b(), say, could usefully be called both by function1() and by function3(), for example. Indeed, if we do a good job in designing the program - in deciding just exactly how "best" to break it down - we may be able to achieve quite a high level of such re-usability!

Note that in laying out the outline example program above, I have chosen to place the function definitions is essentially "reverse" order - if function1() would call function1a() at run-time, then I have placed the definition of function1a() before the definition of function1() in the C source file. This ordering is not absolutely required, but it is an excellent rule of thumb - and you should stick to it unless and until you have some very good reason for diverging from it. But be clear on this point: the order in which the function definitions appear in the source file has no effect whatsoever on the order in which the functions will actually be invoked. The function called main() will always be the first function invoked, regardless of whether it is positioned at the top, the middle, or the bottom, of the source file! And the next function to be invoked will depend exclusively on what statement in main() causes a function to be invoked; and whatever function that actually is, it will be invoked next regardless of whether it is located in the source file before or after the definition of the function main(). The ordering of the functions in the source file affects how the compiler translates the file - and it is to facilitate the compiler that I give you the rule of thumb of ordering the definitions in reverse order to the order in which they are invoked. Ordering them in this way ensures that the compiler never has to attempt to translate a call or invocation of a function without already having translated the definition of the function - and it turns out that this makes the compiler's job easier, and can avoid a variety of compile time problems and errors. But regardless of whether this ordering is adopted or not, it will not affect the order of function execution at run-time!




Document: Software Engineering 1: Course Notes

next Moving Data Around
up Chapter 2 pp. 20-26
previous Chapter 2 pp. 20-26



McMullin@ugmail.eeng.dcu.ie
Wed Apr 12 19:40:14 BST 1995