Chapter 1 - Introduction to functions
Writing a simple function
A C program contains one or more functions. Every C program must have an entry point, a point where execution begins, and this function is normally called main. There is a special kind of C implementation, called a freestanding implementation, where different rules apply, but normally the entry point is called main.
For C to be any use to us, we have to be able to write our own functions. Here, then, is a program with two functions, main and demonstrate, which will demonstrate our own custom-written function (we will call these user functions). The name demonstrate is not a name that the C language specifies, but a name selected to describe its purpose in this tutorial. If you wish to try this program for yourself, you might call it demonstrate.c (although of course you can choose whatever name you like, as long as it's a name that your compiler can handle).
int demonstrate(void) { return 6; } int main(void) { demonstrate(); return 0; }
demonstrate.c program explanation
This program illustrates two important points -- how to write your own C function, and how to call the function. The demonstrate function takes no input, and performs no calculations. All it does is return the value 6 to its caller. Since 6 is an int (a kind, or type, of value), it agrees in type with the return type of the demonstrate function. Don't worry about this for now!
The main function calls demonstrate simply by quoting its name and a pair of matching parentheses: ( and ). Since we don't have to send anything to the demonstrate function, there is nothing between the parentheses.
Even though the demonstrate function appears first in the program, the entry point is still main. Many people prefer to put main at the top, but to do so requires a little more work, which we'll come to in a short while.
Execution, then, begins in main, and proceeds with the first statement after the opening brace. This statement is:
demonstrate();
The semicolon terminates the statement. Since this statement is a function call, the program suspends execution of main, but remembers where it had got up to. Control now passes to the demonstrate function, where the return statement return 6; is executed. This return statement has the effect of terminating execution of the demonstrate function, ceding control back to main. Execution in main now picks up where it left off. In this simple example, no attempt is made to use the value returned by demonstrate. Instead, control passes to the next statement in main, which is the return statement that terminates the function (and, because it's main, it also terminates the whole program).
This idea of control passing from one function to another (or indeed from one statement to another) is known as control flow.
The following image is a call graph of our demonstration program:
This call graph shows all the functions that the main function calls, so it should come as no surprise to see that demonstrate appears in the diagram as well. The arrow indicates that it is main that calls demonstrate, not the other way round.
Function prototypes
The C compiler reads the C source file from top to bottom. When it encounters a function call (such as the call in main to execute the demonstrate function), it needs to know what kind of a function is being called. In our example, that wasn't a problem because it had already seen the demonstrate function. But what if we wanted to put the functions the other way around?
int main(void) { demonstrate(); return 0; } int demonstrate(void) { return 6; }
When this code is compiled, the compiler will complain. For example, the gcc implementation reports:
demonstrate.c: In function ‘main’: demonstrate.c:3:3: warning: implicit declaration of function ‘demonstrate’ [-Wimplicit-function-declaration] demonstrate.c:3:3: warning: nested extern declaration of ‘demonstrate’ [-Wnested-externs] demonstrate.c: At top level: demonstrate.c:7:5: warning: no previous prototype for ‘demonstrate’ [-Wmissing-prototypes]
Although this looks rather intimidating at first sight, all it means is that, when the compiler was reading main, it came across a call to a function that it didn't yet know anything about.
To fix this, we have to give the compiler some kind of advance notification about the demonstrate function. We do this by giving the compiler a prototype, a bare-bones description of the kind of function we are calling:
int demonstrate(void); int main(void) { demonstrate(); return 0; } int demonstrate(void) { return 6; }
The line
int demonstrate(void);
is a prototype. It is not executable code. It doesn't define the function, and it doesn't call the function. It is simply a declaration that the function exists, and is of a particular type. This fairly minimal description is sufficient to satisfy the compiler, and allows it to generate the calling code for demonstrate. We are then free to place the definition of demonstrate elsewhere in the source file (or even in another source file entirely!). The demonstrate function must, however, exist somewhere. Otherwise, the implementation will fail at the link stage, which is the final stage of preparing an executable file.
It is not only main that can call other functions. Any function can call any other function for which a prototype is visible, as is shown below:
int func1(void); int func2(void); int func3(void); int func4(void); int main(void) { func1(); func2(); func1(); return 0; } int func1(void) { func3(); return 1; } int func2(void) { return 2; } int func3(void) { func4(); return 3; } int func4(void) { func2(); return 4; }
To save you a lot of page-up and page-down, I've used the freedom that C gives me to write most of these functions on a single line. As you can see from the code, main starts by calling func1. Execution of main is suspended while func1 does its work (which consists of a call to func3, which consists of a call to func4, which consists of a call to func2.
When func1 eventually halts, main calls func2. When that function returns, main calls func1 again. There is no reason why a function can't call another function twice (or more). And then, when func1 returns for the second time, main terminates. Here is the call graph for this program:
This program executes func1 twice, func3 twice (once for each run of func1), func4 twice (once for each run of func3), and func2 three times (once for each run of func4, and once directly from main).
Of course, none of these functions does anything useful. It's time to change that, which we'll do in the next chapter.
Summary
In this chapter, you learned how to write a simple C function, and how to create a function prototype. You learned how functions can call functions, which can call functions, and so on.
In the next chapter, we will learn about some of C's library functions, and we will take our first steps towards understanding the C preprocessor.
Progress
Terminology
- user function
- control flow
- function
- main
- parameter list
- statement terminator
- function prototype