C40. C Functions


A function is simply a series of statements that have been grouped together and given a name. Although the term "function" comes from mathematics, C functions don't always resemble math functions. In C, a function doesn't necessarily have arguments, nor does it necessarily compute a value. (In some programming languages, a "function" returns a value, whereas a "procedure" doesn't. C lacks this distinction.)
Functions are the building blocks of C programs. Each function is essentially a small program, with its own declarations and statements. Using functions, we can divide a program into small pieces that are easier for us—and others—to under­stand and modify. Functions can take some of the tedium out of programming by allowing us to avoid duplicating code that's used more than once. Moreover, func­tions are reusable: we can take a function that was originally part of one program and use it in others.

Defining and Calling Functions
Before we go over the formal rules for defining a function, let's look at three sim­ple programs that define functions.
Function Definitions
 let's look at the general form of a function definition:
return-type function-name ( parameters ) {
declarations statements
The return type of a function is the type of value that the function returns.
The following rules govern the return type:
Functions may not return arrays, but there are no other restrictions on the return type.
Specifying that the return type is void indicates that the function doesn't return a value.

One way to avoid the problem of call-before-definition is to arrange the pro­gram so that the definition of each function precedes all its calls. Unfortunately, such an arrangement doesn't always exist, and even when it does, it may make the program harder to understand by putting its function definitions in an unnatural order.
Fortunately. C offers a better solution: declare each function before calling it. A Junction declaration provides the compiler with a brief glimpse at a function whose full definition will appear later. A function declaration resembles the first line of a function definition with a semicolon added at the end:
return-type function-name ( parameters ) ;
Needless to say. the declaration of a function must be consistent with the function's definition.
Let's review the difference between a parameter and an argument. Parameters appear in function definitions; they're dummy names that represent values to be supplied when the function is called. Arguments are expressions that appear in function calls. When the distinction between argument and parameter isn't impor­tant, I'll sometimes use argument to mean either.
In C, arguments are passed by value: when a function is called, each argument is evaluated and its value assigned to the corresponding parameter. Since the parameter contains a copy of the argument's value, any changes made to the parameter during the execution of the function don't affect the argument. In effect, each parameter behaves like a variable that's been initialized to the value of the matching argument.
The fact that arguments are passed by value has both advantages and disad­vantages. Since a parameter can be modified without affecting the corresponding argument, we can use parameters as variables within the function, thereby reduc­ing the number of genuine variables needed.

Argument Conversions
C allows function calls in which the types of the arguments don't match the types of the parameters. The rules governing how the arguments are converted depend on whether or not the compiler has seen a prototype for the function (or the function's full definition) prior to the call:
The compiler has encountered a prototype prior to the call. The value of each argument is implicitly converted to the type of the corresponding param­eter as if by assignment. For example, if an int argument is passed to a func­tion that was expecting a double, the argument is converted to double automatically.
The compiler has not encountered a prototype prior to the call. The compiler performs the default argument promotions: (1) float arguments are con­vened to double. (2) The integral promotions are performed, causing char and short arguments to be converted to int.
The return Statement
A non-void function must use the return statement to specify what value it will return. The return statement has the form
return expression ;
The expression is often just a constant or variable:
return 0;
return status;

Program Termination
Since main is a function, it must have a return type. Normally, the return type of main is int. which is why the programs we've seen so far have defined main in the following way:
int main(void) {
Older C programs often omit main's return type, taking advantage of the fact that it traditionally defaults to int:
main() {
Omitting the return type of a function isn't legal in C99, so it's best to avoid this practice. Omitting the word void in main's parameter list remains legal, but—as a matter of style—it's best to be explicit about the fact that main has no parameters. (We'll see later that main sometimes does have two parameters, usually 13.7 named argc and argv.)
The value returned by main is a status code that—in some operating sys- ^ terns—can be tested when the program terminates, main should return 0 if the program terminates normally; to indicate abnormal termination, main should return a value other than 0. (Actually, there's no rule to prevent us from using the return value for other purposes.) It's good practice to make sure that every C pro­gram returns a status code, even if there are no plans to use it, since someone run­ning the program later may decide to test it.

The exit Function
Executing a return statement in main is one way to terminate a program. Another is calling the exitfunction,which belongs to .
The argu­ment passed to exit has the same meaning as main's returnvalue:bothindicate the program's status at termination.
To indicate normal termination, we'd pass 0:
exit(0);/* normal termination */
Since 0 is a bit cryptic, C allows us to pass EXIT_SUCCESS instead (the effect is the same):
exit(EXITSUCCESS); /* normal termination */
Passing EXIT_FAILURE indicates abnormal termination:
exit(EXIT_FAILURE); /* abnormal termination */
EXIT_SUCCESS and EXIT_FAILURE are macros defined in . The values of EXIT_SUCCESS and EXIT_FAILURE are implementation- defined; typical values are 0 and I, respectively.As methods of terminating a program, return and exit are closely related. In fact, the statement
return expression;
in main is equivalent to    exit (expression) ;
The difference between return and exit is that exit causes program termina­tion regardless of which function calls it. The return statement causes program termination only when it appears in the main function. Some programmers use exit exclusively to make it easier to locate all exit points in a program.
Local Variables
A variable declared in the body of a function is said to be local to the function. In the following function, sum is a local variable. By default, local variables have the following properties:
Automatic storage duration. The storage duration (or extent) of a variable is the portion of program execution during which storage for the variable exists. Storage for a local variable is "automatically" allocated when the enclosing function is called and deallocated when the function returns, so the variable is said to have automatic storage duration. A local variable doesn't retain its value when its enclosing function returns. When the function is called again, there's no guarantee that the variable will still have its old value.
Block scope. The scope of a variable is the portion of the program text in which the variable can be referenced. A local variable has block scope: it is visible from its point of declaration to the end of the enclosing function body. Since the scope of a local variable doesn't extend beyond the function to which it belongs, other functions can use the same name for other pur­poses.

Static Local Variables
Putting the word static in the declaration of a local variable causes it to have static storage duration instead of automatic storage duration. A variable with static storage duration has a permanent storage location, so it retains its value throughout the execution of the program. Consider the following function:
void f(void) {
static int i; /* static local variable */
Since the local variable i has been declared static, it occupies the same mem­ory location throughout the execution of the program. When f returns, i won't lose its value.
A static local variable still has block scope, so it's not visible to other func­tions. In a nutshell, a static variable is a place to hide data from other functions but retain it for future calls of the same function.

External Variables
Passing arguments is one way to transmit information to a function. Functions can also communicate through external variables—variables that are declared outside the body of any function.
The properties of external variables (or global variables, as they're sometimes called) are different from those of local variables:
Static storage duration. External variables have static storage duration, just like local variables that have been declared static. A value stored in an external variable will stay there indefinitely.
File scope. An external variable has file scope: it is visible from its point of declaration to the end of the enclosing file. As a result, an external variable can be accessed (and potentially modified) by all functions that follow its dec­laration.
Example: Using External Variables to Implement a Stack
To illustrate how external variables might be used, let's look at a data structure known as a stack. (Slacks are an abstract concept, not a C feature; they can be implemented in most programming languages.) A stack, like an array, can store multiple data items of the same type. However, the operations 011 a stack are lim­ited: we can either push an item onto the stack (add it to one end—the "stack top") or pop it from the stack (remove it from the same end). Examining or modifying an item that's not at the top of the stack is forbidden.
One way to implement a stack in C is to store its items in an array, which we'll call contents. A separate integer variable named top marks the position of the stack top. When Ihe stack is empty, top has the value 0. To push an item on the stack, we simply store the item in contents at the position indicated by top, then increment top. Popping an item requires decrementing top. then using it as an index into contents to fetch the item that's being popped.

Pros and Cons of External Variables
External variables are convenient when many functions must share a variable or when a few functions share a large number of variables. In most cases, however, it's better for functions to communicate through parameters rather than by sharing variables.

If an external variable is assigned an incorrect value, it may be difficult to identify the guilty function. It's like trying to solve a murder committed at a crowded party—there's no easy way to narrow the list of suspects.
Functions that rely on external variables are hard to reuse in other programs. A function that depends on external variables isn't self-contained; to reuse the function, we'll have to drag along any external variables that it needs.
Many C programmers rely far too much on external variables. One common abuse: using the same external variable for different purposes in different func­tions. Suppose that several functions need a variable named i to control a for statement. Instead of declaring i in each function that uses it, some programmers declare it at the top of the program, thereby making the variable visible to all func­tions. This practice is poor not only for the reasons listed earlier, but also because it's misleading; someone reading the program later may think that the uses of the variable are related, when in fact they're not.
When you use external variables, make sure they have meaningful names. (Local variables don't always need meaningful names: it's often hard to think of a better name than i for the control variable in a for loop.) If you find yourself using names like i and temp for external variables, that's a clue that perhaps they should really be local variables.

In a C program, the same identifier may have several different meanings. C's scope rules enable the programmer (and the compiler) to determine which meaning is relevant at a given point in the program.
Here's the most important scope rule: When a declaration inside a block names an identifier that's already visible (because it has file scope or because it's declared in an enclosing block), the new declaration temporarily "hides" the old one, and the identifier takes on a new meaning. At the end of the block, the identi­fier regains its old meaning.

No comments: