Functions

If-statements and loops allow us to alter the normal sequential execution of statements in a program. Instead of always executing the next line of the source code, these programming constructs allow us to select which statements to execute or how many times to execute a statement or block of statements. Function calls also allow programmers to control the order in which the computer executes the statements in a program. Because you have used library functions, you already have at least an intuitive knowledge of how functions affect the flow of control in a program. When you call a function, you expect that the code in the function will execute and that control will then return to the next statement after the function call. We can view a function call as a sort of "side trip." The function call transfers control from the caller to the first statement in the code of the function. The statements in the function execute and then the next statement after the call executes. The following shows diagrammatically how flow of control works with functions.

fcn1.jpg (18483 bytes)

A function is a way of "delegating" work. The program needs to accomplish certain tasks. Instead of trying to do all the work itself (in the main function), it requests "helpers" to do the work for it. In other words, the main program's real job is simply to coordinate and call on other modules to do the work that needs to be done. In turn, these workers (other functions) may call on "underlings" (still other workers or functions) to help them do these tasks. Just as a human worker would need to get certain information from the boss and would also need to give new information back to the boss after finishing a job, the main functionor calling module) must typically provide certain information to a subordinate function that will help do the job at hand and that subordinate function will return information to the caller.

In many cases, it would be possible to simply paste the code for the function in the main program in place of the function call. Later, you will learn that this is not always possible, but even when it is, having a function is better for several reasons. One of the most obvious of these is that it will often make a program smaller. Consider how many times you have used the printf function, for example. If you were to paste all the code for printf in your program every time you wanted to print something, your programs would be much longer. They would also be more difficult to understand, since instead of having a name for printing, you would have some rather cryptic statements that perform the output. 

Often, beginning programmers resist writing functions. Mainly this reluctance arises because writing functions is something new and therefore unknown. Another reason is that because most programming assignments for beginners require only a small amount of code. This means that the programmer can understand the whole program fairly easily. Of course, it should be obvious to you that if you start writing programs with functions, the newness will quickly wear off and you will feel increasingly comfortable with them. Nevertheless, it is important to understand all the benefits that functions provide so that you will have the proper motivation to learn how to use them correctly.

Benefits of using functions

Clarity, organization, and understandability

When a program is quite small and simple, that is, when it performs a single task, a single function is generally sufficient. The amount of code in the function is small and relatively easy to understand. When programs become larger, however, the code becomes more complex and harder to comprehend. Most commercial programs are impressively large. The only way that such a program can be manageable is if the programmers have organized it in logical sections. Functions allow such an organization to exist. Every large program consists of a number of small tasks that work together to perform more complex operations. With some types of programs, this is particularly easy to understand. For example, if you think about a word processing program, you will realize that it is really a collection of tasks. One task is to accept and display textual input (it allows the user to type things), other tasks allow the user to copy, paste, delete, insert, and cut text. Still other tasks let the user highlight and format, scroll the window, open or close a file, insert a table, etcetera.

The structure of a good top-down design generally gives a programmer a good deal of guidance in terms of deciding how to divide a program into component functions. Most of the time, each separate module of the design becomes a function in the program. It is common for one function to call one or more other functions. For example, main may call a function that in turn calls two more functions. Thus the calling sequence of functions is hierarchical, just as the modules in a top-down design are. A modular program will have a main function that contains mostly calls to other functions. In this sense, main is like the CEO in a business. It actually does very little in the way of the "nuts and bolts" of the job. Instead, its role is to set other functions to perform the work. In turn, the functions that the main function calls may themselves correspond to mid-level management. That is, they may may in turn call other functions to do the subtasks that make up the function's job. Of course, eventually, bottom-level functions (those that do not call any other functions, apart from library functions) perform the steps to accomplish what should, at this level, be a very simple job.

This hierarchical program structure provides what is perhaps the most important benefit of functions. That is, it makes the program relatively easy to understand at various levels. If a human reader of a function wants to gain a quick understanding of a large program, the main function will give a general overview. If the names of the functions are meaningful and descriptive, they will tell the reader what basic actions the program will perform and in what order. Exactly how the program will accomplish those actions is unknown at this level, but those are details that only complicate the "big picture." If the reader wishes to understand those details, he or she can then examine the functions that main calls. At the bottom level, the functions will perform extremely simple tasks and thus will contain only a small number of statements. Because each function is short and has a small specific job to do, each is easy to understand.

Ease of debugging

Modularizing a program through the appropriate use of functions offers other benefits in addition to increased clarity and understandability. As a beginning programmer, perhaps one of the most important of these benefits is that debugging becomes easier when a program is modular. It is simple to understand why. In a well-designed program, each function does a single, simple task. If a problem arises in a program, it is typically fairly easy to determine what part of the program is at fault. Since appropriate functions are independent from each other and contain only a few lines of code, once you have determined which task is incorrect, finding the error in those few lines of code is a small job. On the other hand, if functions are long or if the programmer has not written them correctly so that they are independent, the number of lines of code that could be at fault increases rapidly. The more code that needs checking, the longer it takes to find the problem. The difference is akin to finding the proverbial needle in a haystack as opposed to finding a needle in a few straws.

Reusable code

Functions that are independent from each other also provides another important benefit. When you write a low-level function that performs a simple task for a program, it often turns out that some other program will need to perform the same task. The more you program, the more you will find that this is true. In fact, this is the basis of the C library functions. Each has broad applicability and is useful in many different kinds of programs. If you write a function that performs such a task you can use that function in other programs. This makes writing programs easier and speeds program development time. We call this ability code reusability.

A wise programmer will always consider ways that a piece of code might be useful in other programs and decide on this basis what belongs in a function and what does not. For instance, suppose you have a program that at some point must request the user to input a number that represents the radius of a circle and then find and print the area of the circle. The programmer might realize that finding the area of a circle is a task that may prove useful in other programs and so would decide that a function should perform this computation. One approach would be to have the function prompt for the radius, compute the area and then print it. Unfortunately, this solution is not the most general. Because the function does both input and output, it is only useful in identical situations. In a program that computes the radius and then uses the area of the circle in another computation, the function does not work properly. The problem is that the function does more than a single, simple task; in fact, it does three: it requests and reads input, it computes the area of a circle from a radius, and finally, it prints a result. A better design would be to have the function do nothing more than compute the area of the circle and allow the caller of the function to determine how to get the radius and how to use the result.

Modifiability

The independence of functions also means that programmers can view functions as "plug-in" modules. When it becomes necessary to modify a program to improve performance or increase its capabilities, it is often possible to achieve the modifications simply by adding or replacing existing functions in the original program. Frequently, several algorithms exist to accomplish a single task. In most such cases, the programmer weighs the costs and benefits of the various choices. For instance, one algorithm may result in faster execution, another may offer increased clarity, and yet another may simply be faster to code. Sometimes a programmer will elect the algorithm that is quickest to write (perhaps because of deadline constraints), because he or she reasons that the task will probably not execute often. Later it may turn out that this is not true. It then becomes possible to speed up the program by replacing the slow code with a faster algorithm. If a single function or a small set of functions implements the slow algorithm, the programmer can easily replace the slow function(s) with faster ones.

If a programmer wishes to add new features to a program and the program is modular, typically it will be possible to implement the additional features by adding new functions to the program. In this situation, the existing program still works as before. The only change will be to add calls to the new functions. Apart from making the program quicker to modify, this also helps prevent introducing new errors into the original code. The fewer changes the programmer needs to make, the smaller the chance of "breaking" code that was correct before the changes. 

Two kinds of functions

It is possible to write two distinct kinds of functions in C. Some C programmers mix the different kinds, but we will see why it is better to keep them completely separate. In fact, some programming languages use the generic name subroutine or subprogram to include both types of function. These languages then use the more specific names function and procedure to distinquish between the two types. They also specify different syntax for functions and procedures and provide compiler support to ensure that the programmer does not mix up the two kinds of subroutine. C does not make the distinction "official", but it does allow for both kinds of subprogram.

The first type of subroutine is what we might call a "true" or "pure" function and it corresponds to what other languages would specifically define as a "function".  C subroutines of this sort are analogous to the functions you have seen in mathematics. Such functions take one or more arguments, perform some computation using those arguments and then return a single value. For instance, if we define f to be the squaring function, then f (x) returns x2. One thing that you intuitively know, although you may not have explicitly thought about it before, is that computing f (x) does not change the value of x at all. Instead, it produces a new value that did not exist before. This is an important feature of a pure function. If we call such a function in C, we must embed the call in an expression that uses the new value that the function produces. If we do not, we lose the value and the call is wasted effort. Each "true" function returns a value that belongs to a particular data type. For example, it may return an integer, a real number, a character, or a boolean value. It can never return more than one value, however, nor will it ever alter any other value. Some library functions that you have used of this type are the fileEnd and eoln functions from the fileio library. Both of these functions return a single boolean value.

The second type of function is a procedural function and corresponds to what other languages would call a "procedure". Sometimes you will hear people call this a void function. This latter term comes from the fact that such a subroutine returns nothing. At first, this may seem odd. If a function does not return anything, how could it be useful? Nevertheless, you have used several library functions in this way. For example, when you call printf, you are not interested in what it returns to you, but the action it performs: screen output. Similarly, you call scanf not so that it will return a value, but so it will initialize one or more variables. If we were only able to use scanf to initialize one variable at a time, we could actually write it as a "true" function, but because we can use it to read multiple values, it must be a procedural function instead. This latter point is the most important feature of procedural functions. They allow a function to alter more than one value. Notice that when a procedural function alters an argument, it does something that a "true" function does not. If we wrote the squaring function as a procedural function, then computing f (x) would permanently change x. Before calling the function for example, x might have the value 3. After calling the function, x would have the value 9. Because a procedural function can alter its arguments, it allows us to use this type of subprogram to make changes to several variables at once.

The actions that a procedural function performs are side effects. A side effect is any change to the state of the machine (e.g., a change to the value of a variable, a change to a file or to the screen, etc.) that occurs in one module but affect other modules. In other words, they are changes that a subroutine makes that are "invisible" in the calling code (no statement in the caller makes the change), but persist once the subprogram returns control to the caller. As you will soon see, when we write a procedural function, the call will contain syntactic cues (the ampersand operator that you have seen with scanf) that such side effects will, or at least may, occur.

Many of the standard C library functions are actually neither strictly procedural nor strictly pure functions. Instead they are a mixture of both kinds of functions. That is, they return a value, but they also make changes to one or more of their arguments. This type of function is often problematical and never necessary. Consider, for example, the scanf function. It makes changes to the parameters that follow the format string but it also returns an integer value that represents either the number of values it read. Programmers virtually never care about or use the return value of scanf. Many of the string library functions take a string variable as an argument and return the same string as well. They make changes to their argument which will persist when the functions return to their callers, so assigning the return value to a string variable is unnecessary and redundant. In other words, returning the string is simply pointless. Worse than simply resulting in redundant or unnecessary work, mixing the two kinds of subroutines can actually create problems. Assume for a moment that we have written a subprogram called doubleIt that will take an integer argument, double it and return the doubled value. Assume also that the doubling of the argument persists in the calling code. Because the function returns a value, we can use the call as an operand in an expression. When the function terminates, its return value becomes the actual operand. Now consider a program fragment like the following:

main ()
{
  int i, j = 3;

  i = j + doubleIt (&j);
  ...
}

Of course, when doubleIt completes its execution, we expect the value of j to be 6. If you analyze the statement, however, chances are that you will expect the assigned value of i to be 9. Whether it is or not depends on the implementation of the compiler. If the compiler generates code to evaluate the operands in reverse order, j will already hold the value 6 before the computer fetches the value of the first operand. In this case, the value of i will be 12 rather than 9. Because both interpretations of the statement are reasonable, we say that the expression is ambiguous. You might argue that you would never use doubleIt in this fashion. For example, if we changed the statement above to:

  doubleIt (&j);
  i = j + j;

no ambiguity arises. It is clear that i will hold the value 12 after the statements execute. In this case, however, having doubleIt return a value in addition to altering its argument is pointless; we do not use the return value. Similarly, if we first initialize i to some value and use the statement:

  i = i + doubleIt (&j);

there is no confusion about the result since we have not used the function argument again in the expression. The problem here is that programs are generally not static. They change over time as programmers modify them to increase performance or add new features. The original programmer has no guarantee that programmers working on subsequent modifications will use the function in a clear and unambiguous way.

Because of the problems that arise with mixed-type functions, it is best to make a firm stylistic rule: "true" functions that return a value must never alter their arguments and procedural functions that alter their arguments must never return a value. You will need to remember that this is a rule of style, that is, the compiler will not enforce it. You must rely on your own thoughtfulness and self-discipline to avoid breaking this rule.

Function syntax

By now, you should be convinced that using subroutinesin your programs can offer you several important advantages. The next step is to see how to write them. Of course, you have been writing the main function since you wrote your first program and you have been calling functions from the C libraries as well, so you already have a solid foundation for understanding the syntax of function definitions and function calls. The definition of a function consists of its header and body. The header for main is most often nothing more than its name followed by a pair of empty parentheses. In other cases, the header will also include arguments and a data type name that specifies the type of value that the subroutine will return. The body for main is all the declarations and code between the braces that follow the header, including any variable declarations that may appear. Similarly, the body of other subprograms will be the declarations and code that appear between braces following the header. It is also possible to declare a function in C. A function declaration is its header followed by a semicolon. In C, the most common name for a function declaration is a function prototype. We will see shortly why the need for function declarations is relatively rare.

Functions in the context of the program

To begin, we should see where function definitions belong in the program file. When a line of code calls a subroutine, the compiler must already have seen either the subroutine's declaration or the definition. If it has not, it will make some   assumptions about the function. When the compiler finally encounters the function definition, it often discovers that these assumptions were incorrect. At that moment, the compiler will generate an error message. Usually, the error message will say something about a type mismatch in the redeclaration of the function. This can be confusing, since the program contains only one definition or declaration for the function!

All of this adds up to one thing: a subroutine's definition must precede any statement that calls it. This means that functions appear before main in the source code file. Not only that, but if one subroutine calls another, the definition of the called subprogram must appear before the definition of the caller. In short, we would expect to see bottom-level functions at the top of the file, main at the end, and intermediate level functions in between. Be sure that you understand that when a program executes, it will always begin with the first statement in the main function. This is true even though you have subroutine definitions that precede main in the program file.

Some C programmers prefer to place function prototypes at the beginning of the program file, then the main function, and finally the function definitions at the end of the file. This method has at least two disadvantages. First, writing all the prototypes is redundant. The header for each function must appear with its definition later in the program, so prototyping requires the programmer to type the same header twice for every function. This makes the program take longer to type and makes the program itself longer, requiring more disk space and more paper to print. If the programmer does not use cut and paste facilities to include the function prototypes, it is also more error prone. Any small difference between the prototype and the header in the definition will cause a compiler error. In large programs, the function definition may be far away from the beginning of the file where the prototypes are, so chances of introducing such an error are greater.

The disadvantages just listed are relatively minor compared to the second reason for placing the function definitions before any calls to the function. When you put the function definition before its point of call, you have the assistance of the compiler in terms of organizing the modules of your program. Because the compiler will complain about function redeclaration when it finds a call to a function before the function's definition, it forces the programmer to arrange the functions in a particular order. Bottom-level functions appear first, the functions that call bottom-level functions appear next, etcetera, all the way down to the higher level functions that appear just before main. This organization helps a human reader of the program. First, it is often possible to guess approximately where a function definition will be in a large program, just by realizing how complex a task it performs. The simpler the task, the nearer to the top of the file the function will be. Furthermore, if a human being sees a function call in a program, he or she knows that the definition of the function will be somewhere above the call. If a programmer uses prototypes, the function definition could be literally anywhere in the file. Placing the definitions before the call imposes an ordering on the program file which directly mirrors the top-down design. The only difference is that the ordering is exactly reversed. In the top-down design, the highest level module appears first and the lowest level modules appear last. In the program, the lowest level functions appear first and the highest level (main) appears last. Since one of the foremost goals of a programmer should always be to make programs as readable as he or she can, it is impossible to overstate the importance of this advantage.

The following syntax template shows a generic layout for a program:
include directives
define directives

low-level function definitions
intermediate-level function definitions
high-level function definitions

main ()
{
  variable declarations

  statements
}

One situation can occur where a function prototype is truly necessary. In relatively rare instances, you may write a program in which two subroutines call each other. For example, suppose you write a function A that calls another function B. Function B also calls function A. Plainly, it is impossible to place the definition for function A before the definition for function B and the definition for B before the definition for A; both definitions must seemingly come first! In this case, you would need to include a prototype for one of the functions before the definitions of the other two. You might decide to use a prototype for function B. This would appear first, followed by the definition for function A, and finally the definition for function B. This structure is called mutual or indirect recursion. It can be a powerful tool for solving certain kinds of problems, but it is fairly rare. Certainly, you should not expect to need to use it for some time to come.

Function calls

You have already called C library functions in the programs you have written. You know therefore that to call a function, you use its name followed by an argument or parameter list inside parentheses. The precise term for an argument that appears in a subroutine call is an actual parameter. As you will see, a function definition will also include a parameter list, but it will differ somewhat from the parameter list in the call. A parameter within a function definition is a formal parameter. Formal parameters, unlike actual parameters, will include a data type name. These names refer to special properties of the two kinds of parameters. When you call a function, the parameters you pass represent actual, concrete values. The compiler can deduce their types in one of two ways. First, if the actual parameter is a variable, it will have a declaration in the same module as the the call. If the actual parameter is a constant or an expression, the compiler determines the type from the rules for constant literal syntax or expression evaluation. This means that you do not need to specify the data types of arguments in the function call. You know this already, but frequently, when beginning programmers start writing function definitions (which require data type names), they become confused and start adding data type names to function calls.

The formal parameters in the header of a function definition represent "place holders." They give the types of the arguments but do not have any particular value. They tell the compiler how many parameters the subroutine will receive, what their data types will be, and the names for these values that will appear within the body of the subprogram. This information is necessary to allow the compiler to generate the correct code to set up a dedicated set of memory locations for the subroutine, to check for correct data type correspondence, and to associate names with memory locations. In other words, formal parameters specify the form of the arguments, but not their actual values.

Although you have not yet used a function that requires no arguments, such functions can and do exist. It is important to remember that in this case, the parentheses after the function name are still essential to the function call; they simply contain no argument list. If you attempt to call a subroutine without the parentheses, the compiler will often still compile the program without error, but the result will not be what you expect. This is because the name of a function without the parentheses is really a name for the address of the memory location that contains the first instruction of the function. This means that you must remember the parentheses; the compiler will not help you. The basic syntax template for a function call should be familiar to you. For a procedural function, the call follows this form:

function_name (actual_parameter_list);

If you are calling a "true" function, the template is the same, but the call will appear as part of an expression. The kind of expression will depend on the data type the function returns. It is useful to think of a "true" function call as a constant of a given type, because such a function call can appear anywhere that a constant of the same type could appear. For example, if a function returns an int or float, you might expect to see the call as an operand for an arithmetic operator, the right-hand side of an assignment statement, or as an argument to some other function. If a function returns a boolean value, you will most often see the call used as a condition for a loop or an if-statement. The following table lists the common uses of a "true" function call:

Use Example
  1. as the right-hand side of an assignment statement:
  2. as an argument to another function call:
  3. as an operand in an expression:
  4. as a condition:
val = average (x,y);
printf ("%f", average (x, y))
;
val = average (x, y) + 2;
while (!eoln (in)) { ... }

Because a "true" function call evaluates to a constant value, the one place that a such a call can never appear is as the left-hand side of an assignment statement. It is also incorrect to call a "true" function without having it be part of an expression. Such a call is equivalent to a statement such as:

main ()
{
  3;
}

The compiler will compile such a statement, but the statement does nothing at all. In the same way, a call to a "true" function that is not part of an expression does nothing, but it takes time and computer resources to do it!

You have seen that various kinds of things can appear in the argument list and that a comma follows each argument except  the last. In general, arguments to a subroutine might be variable names, named constants, constant literals, expressions, "true" function calls, or the address of a variable (such as the arguments to scanf and fscanf that follow the format string). You will learn shortly that certain kinds of arguments are not legal in some circumstances.

Function definitions

The syntax template for a function definition is also mostly familiar to you, since you have been writing a main function in every program. The main function is somewhat different from other subroutines in a couple of ways. Although it is possible for main to have arguments, it usually does not. Most other subprograms that you will write will have parameters and thus a non-empty formal parameter list. In addition, all function definitions other than main will have a stated return type. This is the C data type for the value that the function returns. If you are writing a procedural function, the return type will always be void. In other respects, functions you write will be the same syntactically as the main function. The full syntax template for a function definition follows:

return_type function_name (formal_parameter_list)
{
  declarations

  statements
}

Notice in particular that any subprogram can declare its own local variables in its declarations section. These variables are not accessible to other functions. This means that the only way for functions to share information with each other is through the parameter list and/or the return value from a "true" function. A subroutine would declare local variables when it needs storage space for values that it requires to be able to perform its specified task. These would be "private" in the sense that no other program module would need to use these values. You can think of them as temporary variables that are only useful while the subprogram is executing.

Parameters

Communication, or information sharing between the caller of a subroutine and the callee (the called subprogram) occurs chiefly through parameters. C provides two ways to pass parameters: by value or by reference. To understand the difference between these methods, you need to know that each subroutine has its own memory space or set of memory locations. This memory space is only available as long as the function is executing. When a high level language statement calls a subroutine, the compiler generates machine language instructions to implement the call. Some of these instructions appear before the CALL instruction and other instructions appear as "prologue" code at the beginning of every subroutine. These instructions allocate a new set of memory locations for the subprogram. At the end of every function, the compiler also generates some "epilogue" code that frees this memory for other uses. A subroutine has no direct access to the memory space of any other subroutine. For this reason, any information that a function uses must reside in the function's own memory space. If a subprogram needs to use data that belongs to its caller, the caller must copy the data to the callee's memory space. The programmer specifies what information the caller will copy by means of the parameter list.

Correspondence between actual and formal parameter lists

The actual and formal parameter lists must correspond to each other in two important ways. First, the ordering of the lists is critical. C matches formal and actual parameters by their respective positions within the lists. The first actual parameter corresponds to the first formal parameter, the second actual parameter corresponds to the second formal parameter, and so forth. To see just how important this ordering is, assume we have a function that computes integer powers for positive exponents:

int posPower (int base, int exponent)
{
  int power = 1;

  while (exponent > 0) {
    power *= base;
    --exponent;
  }
  return power;
}

Suppose you want to call posPower to compute 32. If you call the function with the following statement:

x = posPower (3, 2);

you will get the right answer because the actual parameter 3 will match with the formal parameter base and the actual parameter 2 will correspond to the formal parameter exponent. On the other hand, if you call posPower with:

x = posPower (2, 3); 

you will end up computing 23 instead of 32. This is because now the actual parameter 2 matches the formal parameter base and the actual parameter 3 corresponds to the formal parameter exponent.

The fact that the correspondence between actual and formal parameters is by position is part of what gives subprograms their power. A program can call a single subroutine various times from different places and pass different parameters to it each time. If the names of the actual and formal parameters had to be the same, it would restrict the arguments that at subroutine could receive.

The second way in which actual and formal parameters must correspond to each other is in the number of parameters in each list and in the data types of the parameters. This means that if the formal parameter list for a subroutine includes three parameters, every call to the subroutine must also contain three parameters. The data types for the parameters must also be the same. If they are not, one of two things may happen. In most cases, the compiler will generate either a warning or an error message. The warning message will generally occur if the compiler can make a type coercion between the type of the actual parameter and the type of the formal parameter, but the conversion is "suspicious" in some way (i.e., it would not be a common conversion). The compiler will issue an error message if the types are not compatible, that is, the compiler has no reasonable way to coerce the type of the actual parameter to the type of the formal parameter. In a few cases, the compiler will simply coerce the type of the actual parameter to the type of the formal parameter without any warning or error message. This would typically only happen if the conversion is a common promotion, such as int to float or char to int.

Value Parameters

When you pass a parameter to a subroutine by value, the compiler generates code that will make a copy of the parameter's value into the subroutine's memory space. A function that receives value parameters can assign new values to these parameters, but  because the changes affect only the subroutine's local copy, any changes disappear when the subprogram terminates and returns control to the caller. This means that  the subprogram cannot make changes to the parameters that will persist in the calling code. Consequently, if a function receives only value parameters, it cannot cause side effects other than input and/or output. Plainly then, a "true" function should receive only value parameters. Procedural functions may also receive value parameters, but unless the only side effect they perform is I/O, they will also receive at least one reference parameter.

Actual parameters passed by value can be any kind of expression that resolves to a single value. The following lists various kinds of valid value parameters:

  1. a constant literal (such as a number or character) 
  2. a named constant (declared with a #define directive)
  3. a variable
  4. a call to a "true" function
  5. an arithmetic or logical expression involving any of the above

To pass a variable to a subroutine by value, the actual parameter will consist quite simply of the variable's name. Most of the arguments that you have passed to library functions have been value parameters. The only exceptions have been the parameters you have passed to scanf that use the ampersand operator.

The formal parameter list for a subprogram that receives value parameters will consist of arguments that conform to the following syntax template:
data_type formal_parameter_name
Remember that the formal parameter name does not need to be the same as the actual parameter name (if any) that appears in the function call. Obviously, if the actual parameter is a constant literal, a call to a "true" function, or an arithmetic or logical expression, it has no name, but it is important to remember that if an actual parameter is a variable or named constant, the corresponding formal parameter does not need to have the same name at all. As you have already seen, this is because the compiler establishes the correspondence between actual and formal parameters by their relative positions in the parameter lists, not by their names.

When you pass a parameter by value, you will use the formal parameter name as if you had declared it as a local variable. That is, you will treat the parameter as you do any other variable. To use it, you will simply type its name, just as you have always done in the main function. This may seem like a strange thing to say at this point, since it probably would not occur to you to treat it any other way. The next section, however, introduces reference parameters, and you will see that you will need to use those using a special syntax. For this reason, it may be worthwhile to make the distinction now.

Reference parameters

When you pass a parameter to a subroutine by reference, instead of generating code to copy the value of the actual parameter into the subroutine's memory space, the compiler generates code to copy the address of the actual parameter. Although at first this may seem like a minor detail, the effect is radically different and the ramifications are widespread. Having the address of a parameter gives the subprogram indirect access to a memory space other than its own. This means that it can change values of variables that belong to the caller in addition to being able to change values of local variables. In other words, the subroutine has the ability to cause side effects that result in persistent changes to its caller's variables. As a result, "true" functions should not receive reference parameters, since a "true" function should never alter the values of its arguments. Only procedural functions should receive reference parameters. If you violate this rule, you are mixing the two types of subroutines mentioned earlier, with all the consequent problems.

You have used two library functions that receive reference parameters, scanf and fscanf, so you already know that to pass the address of a variable, you will need to use the "address of" (ampersand) operator to specify that you are not passing a variable's value. Because you have used these two library functions specifically to initialize variables, you may not have realized that only variables are valid with the "address of"  operator. If you think about it, this makes perfect sense. To pass the address of something, it obviously must have an address. Of all the different kinds of actual parameters listed above as valid for value parameters, only variables have addresses. This means that you cannot pass expressions, constants, or the results of a call to a "true" function as a reference parameter. The kinds of actual parameters that you can pass by reference are:

  1. a local variable name preceded by the ampersand operator
  2. the name of a formal parameter that was passed by value, preceded by the ampersand operator
  3. the name of a formal parameter that was passed by reference

The syntax for a formal reference parameter must also reflect the fact that the subroutine is receiving an address rather than a value. For instance, if you pass the address of a character variable, the data type of the parameter is not char, but the address of a char. Addresses are not all alike to the C compiler, however. C distinguishes addresses according to the data type the address contains. Therefore, you might pass an int address, a float address, a boolean address, etcetera. The syntax for a formal parameter passed by reference includes an asterisk after the data type name. Within the formal parameter list, you read the asterisk as "address" or "pointer" (another term for an address). Thus, if you have a formal parameter int * i, you would read it as "int address i" or "int pointer i."

Within the body of a subroutine that receives a formal parameter, you must also use a special syntax to allow you to use or change the value of the variable that resides at the address the subprogram has received. For example, assume that you have the following procedural function header:

void doubleBoth (int * num1, int * num2)

The formal parameter list indicates that the subroutine expects to receive the addresses of two integers, which it will call num1 and num2. These names refer not to the numbers themselves, but to the addresses of the numbers. If you included the following statement in the body of this function:

num1 += num1;

you would actually be saying that the address of the memory location where the first number is stored is to change. It is impossible to change the association between a name and a memory location at run time, so this would statement would not be sensible. What you really want to do is to change the value at the address you received. To do this, you must specify that the value at the address named num1 (the value) should double. Rather confusingly, the asterisk operator, placed before the formal parameter name will indicate this. The correct statement would then be:

*num1 += *num1;

You could read this statement as "the value at address num1 becomes the sum of the value at address num1 and the value at address num1." In other words, you pronounce the asterisk here as "the value at address." An alternative way to read the asterisk when it appears in the body of a function is "the contents of."

If you think about it, you will realize that the asterisk operator has two opposite meanings, depending on where it is. In the formal parameter list, the asterisk means "address" but in the body of a subroutine it means "value." It probably would have been better and less confusing if the designers of C had chosen to use the ampersand in the formal parameter list instead of the asterisk to make this distinction clearer. If you study C++ (a derivative of C), you will learn that the designer of C++ made exactly this change.

Beginning programmers often find it confusing to think in terms of addresses and contents of addresses. You will eventually need to understand the differences completely but as you gain more experience, you will feel increasingly comfortable with this concept. For now, though, you can successfully write programs without needing to think too much about it. If you remember to use the asterisk before the name of every formal parameter wherever it occurs in a subroutine, you will be fine.

Passing parameters from one subroutine to another

As your programs become longer, you will find that increasingly you will be calling one subroutine from another. If you pass a local variable, the rules for passing reference and value parameters will be just as you have already seen. Quite often, however, you will need to pass a formal parameter (i.e., a value the caller has received as a parameter) to another subprogram. This can be a little confusing at first, because several possibilities exist. To make the discussion more concrete, assume that you have a function named procedureB which will call another function named procedureA. Assume that the headers for the two subroutines are:

void procedureA (int a1, int * a2) 
{
  ... /* body for procedureA */
}
void procedureB (int b1, int * b2)
{
  ... /* body for procedureB */
}

Both subroutines expect one parameter passed by value and another passed by reference. When procedureB calls procedureA, it could pass b1 to procedureA as the first parameter or as the second parameter. Similarly, it could pass b2 as either the first or second parameter. Each of these situations is a little different. To understand what you need to do in each case, you must remember what kind of thing each identifier in the caller names (a value or an address), what kind of thing the callee expects, how to get an address from a name for a value, and how to get a value from a name for an address. In our example, b1 names an integer value and b2 names the address of an integer. If we want the address of b1, we need to use the ampersand operator, and if we want to get the value at b2, we need to use the asterisk operator. For example, if we want to pass b1 as the second argument to procedureA, we can reason as follows: b1 names a value, procedureA will expect an address as its second parameter, so the actual parameter in the call must derive an address from the value. This means we need to use &b1. Conversely, if we want to pass b2 (which names an address) as the first argument to procedureA (which should be a value), we will need to derive the value at b2. In this case, we will use *b2 to get the value. If we want to pass b1 as the first argument, or b2 as the second argument, we must realize that the name alone already gives us the correct data type. In either instance, we can pass the parameter by using its name without any other operators. The following table summarizes these four possible situations:

passing method of formal
parameter in caller
passing method of formal
parameter for callee
syntax for actual parameter
by reference by reference parameter name alone
by reference by value asterisk operator and parameter name
by value by reference ampersand operator and parameter name
by value by value parameter name alone

The valid calls to procedureA from procedureB would be:

procedureA (b1, b2);	/* value to value, ref to ref */
procedureA (*b2, &b1);  /* ref to value, value to ref */
procedureA (*b2, b2);	/* ref to value, ref to ref */
procedureA (b1, &b1);	/* value to value, value to ref */

Procedural or "true" function? What passing method when?

When you first begin to use subroutines in your programs, you may sometimes feel unsure about whether you should be writing a "true" function or a procedural function or whether you should pass a given parameter by value or by reference. You have some quick "rules of thumb" that can help you decide. First, you should decide whether you will want your subroutine to make changes to two or more values. If so, you will need to write a procedural function. On the other hand, if you only need to change or produce a single value, a "true" function is the better choice, although you certainly could write a procedural function and get the same effect. Another question you should ask yourself is whether your subroutine will produce output or read input. In these cases, you should always write a procedural function. Even if you want to write a subroutine that will read a value, perform some computation on that value and then return the result, you are actually making changes to two different aspects of machine state. I/O always affects the position of the reading pointer, so that constitutes the first change. In addition, you want to produce a value, so that will be a second change. For this reason, a "true" function should not perform I/O operations.

Once you have determined what kind of subroutine to write, you must decide how to pass the parameter(s) to that subroutine. If you are writing a "true" function, you have already answered this question: you will pass all parameters by value. If you are writing a procedural function, you still have a decision to make. The only way to make this determination is to decide whether the subroutine should be able to change the value of a parameter or not. If a change is necessary, you must pass the parameter by reference. If you need to have the same value after the subroutine executes that you had before calling it, you should pass the parameter by value. We can classify the kinds of parameters we pass to subroutines in a different way, that is perhaps more intuitive than thinking about passing by reference or by value. To do this, we introduce three new names for parameters. Although these names use the words "input" and "output", you must not confuse this classification with screen or file I/O. In this case, we use the terms to mean input to a subroutine (information the subroutine needs to do its job) or output from a subroutine (the new values it produces for its caller).

If you pass a piece of data to a subroutine because it needs it to be able to perform the task it does, but the caller does not expect the subroutine to change its value, the data is input for the subroutine and we call it an input parameter. The subroutine will use its value, but when the subroutine returns control to its caller, the parameter will retain its original value.  Sometimes, you will pass a parameter to a subroutine specifically so the subroutine can initialize it. This is why you call scanf or fscanf for instance. In this situation, the original value of the variable at the point of call may be garbage (i.e., there has been no initialization at all of the variable) or it may have a value which your program no longer needs. In either case your are passing an output parameter to the subroutine. The first appearance of such a parameter in the subroutine will always be as the left hand side of an assignment statement or as a reference parameter in a subroutine call within the body of the callee (i.e., an indirect initialization performed by a "helper" subprogram). On other occasions, you may pass a parameter to a subroutine so that the subroutine can both use its original value and later change it so that it has a new value. As long as this new value should persist in the caller, we call this kind of argument an input/output parameter.

If you can determine what kind of parameter you need using these guidelines, you will also know whether to pass the parameter by value or by reference. The following table summarizes these categories.

Parameter category Characteristics Passing method
input the value will be the same after the call as before the call by value
output first appearance in the callee is on the left-hand side of an assignment statement or as a reference parameter for a call in the body of the callee by reference
input/output first appearance in the callee is not as the left-hand side of an assignment statement and not as a reference parameter for a call in the body of the callee, but it will eventually appear in one of these situations so that the value of the parameter may be different after the call by reference

Return values

When you write a "true" function, you must be sure that the function returns a value to its caller. To do this, you will use a return statement. The C keyword return does two things. First, it sets the value to return to the caller. It also causes an immediate exit from the subroutine. The syntax for a return statement is:

return expression;

The data type that results from evaluating the expression must match the return_type of the function. The expression can be a named constant or constant literal, an initialized variable of the same data type as the function's return_type, or an arithmetic or logical expression. Because the return statement causes an immediate exit from the subroutine, you must be sure that your function does not need to do any more work after the return statement. If it does, you should declare a local variable of the same data type as the function's return_type, assign the return value to this variable, insert the statements that perform the rest of the work in the function and only then should the return statement appear.

Another problem that can arise with return statements occurs when you have an if-statement at the end of a "true" function. In this case, it is easy to forget that both the "then-part" and "else-part" must have their own return statements. In other words, no matter which statements a subroutine skips, and which statements it executes, the last statement that it executes must be a return statement. Notice that this is different from saying that the last statement in a "true" function must be a return statement. If the computer never executes the last statement, you will not return anything from the function. Most compilers will generate a warning message if you have not inserted return statements for every possible execution path. You should never ignore such a message.  

Scope Rules

We say a subroutine is active from the moment of the call to the subroutine to the moment it returns control to the caller. The formal parameter names and local variables within a subroutine are only available for use while the subroutine is active. This is simply another way of saying that each subroutine has its own memory space which exists only while the subroutine is executing. Since the names in the formal parameter list and the names of local variables refer to these temporarily available memory locations, it makes sense that they would not be valid in other parts of the program. Another way of saying this is that each subroutine activation creates a scope for names. We also say the the scope of formal parameter names and local variable names is local to the function. You might guess from all this terminology that scope is an important concept for some reason. In fact, because each subroutine activation creates a unique scope, we can reuse identifiers that exist in other scopes. In other words, it would be possible to have a variable named x in every subroutine in a program, and every one of these variables would be unique and would reside in its own separate memory location. Be sure you realize, however, that you cannot have two data items with the same name within the same subroutine!

It is also possible to have identifiers with global scope in C. Such an identifier is available in every part of the program. All the subroutine names in a C program file are global. In other words, they are visible in every part of the file and thus you could never have two subprograms in the same file that also have the same name. To make an identifier global, its declaration must be outside of all subroutines. Interestingly, when we declare or define a C function, we say that its name is outside of its declaration. This probably seems rather odd to you, but we must make this stipulation to keep the scope rules of the language consistent. Because we typically place #define directives at the beginning of the program file (outside of main or any other subprogram), the identifiers we use to name constants are also global and thus available anywhere in the program.

At this point, you may be realizing something dangerous. If you declare a variable at the top of a program and outside of any subroutine, it will also be available in every part of the program. We call this a global variable. Once beginning programmers learn about this possibility, the temptation is to use global variables as a means of sharing information between a subroutine and its caller becomes almost overwhelming. Since a global variable is available everywhere, it is no longer necessary to pass it as a parameter. You no longer need to consider whether you should pass by reference or by value, nor do you need to worry about asterisks and ampersands. Suddenly, life seems so much easier! Don't even think it!!! Using global variables is almost always a terrible idea. Of all the style rules you will learn, this is one of the very most important.  

The reason for not using global variables is not pure sadism, designed to make your programming harder and more burdensome. Instead, it is because the use of global variables itself ultimately makes programming more difficult. It also complicates debugging and program modification and makes code reuse next to impossible. If you make an assignment to a global variable inside a subroutine, you are causing an "unadvertised" side effect. If you look at the call to such a subprogram, it is impossible to guess that the subroutine will cause a change to the global variable. On the other hand, when you pass a parameter to a subprogram by reference, the ampersand operator serves as a signal that the value will probably change as the subroutine executes. It is clear just from looking at the call what can change and what cannot. If you try to reuse a subprogram that operates on a global variable in some future program, the new program must have the same global variable. Furthermore, no part of the new program can rely on the integrity of the value of that global variable. In large programs, the flow of control can become quite complex. Various conditions can determine whether and when a subroutine will execute. In these circumstances, it is difficult or impossible to be sure what the value of a global variable should be or what it will be at any given point in the program.

To make program development and maintenance as simple, safe, and efficient as possible, you should always aim to make each subroutine self-contained, that is, everything necessary to understand the subroutine should be part of the subroutine. You should be able to use a text editor to cut a subroutine from a program and paste it into another, without having to worry about how the module will "fit" into the new program and without having to tailor variables in the new program to coincide with the code in the subprogram. The parameters and return type form the interface between a subprogram and its caller. Any information shared between the subroutine and its caller should be explicitly part of this interface. Using global variables destroys all of this.

In rare situations, a global variable can be useful, but such situations are extremely unusual. In this course, you will never have occasion to use one. Only if you write a program where virtually every subroutine uses the same piece of data is there an argument for using a global variable. Even in this case, it is possible and probably preferable to pass the value as a parameter. Nevertheless, you are likely to see global variables from time to time, either because the programmer was ignorant of the evils of using them, or because the programmer was lazy or sloppy.  For this reason, it is important to be able to recognize them and to understand them.

If we were to use different names for every data item in a program, it would always be easy to determine the data item to which a name referred. In practice, however, programmers tend to reuse names. In this case, both the compiler and a human reader of the program must be able to determine unambiguously which of possibly several data items an identifier names at any point in the program. The guidelines we use to make this determination are the scope rules of the language. In C, the scope rules are fairly simple. First, you already know that one subroutine cannot use the local names in another subroutine, whether those names are for local variables or for the formal parameters of the other subroutine. Thus, if subroutine foo has a local variable x and foo calls another subroutine bar that also has a local variable or formal parameter named x, any reference to x in bar must be to its own x. The second scope rule states that if a local and a global data item share the same name, the local name shadows the global name. This means that the local name temporarily hides the global name (keeps it in the "shadows") as long as the subprogram is active. For instance, assume that a program has a global variable named max and a subroutine baz has a local variable named max. Any reference to max in the body of baz will be to its own local variable. The local name makes the global variable completely inaccessible to baz. The following is an example of such a situation:

int x; 		/* global x */
int foo (int x) /* local x */
{
  int y;
  y = x *2;	/* refers to the local x, which is a copy of the value of i
		   from main */
  return y;
}
main ()
{
  int i = 5, z;
  x = 10;	/* refers to the global x */
  z = foo (i);  /* after the call, z will hold the value 10 */
}