7.1. Function DefinitionsThe definition of a function consists of a function head (or the declarator), and a function block . The function head specifies the name of the function, the type of its return value, and the types and names of its parameters, if any. The statements in the function block specify what the function does. The general form of a function definition is as follows: In the function head, name is the function's name, while type consists of at least one type specifier, which defines the type of the function's return value. The return type may be void or any object type, except array types. Furthermore, type may include the function specifier inline, and/or one of the storage class specifiers extern and static. A function cannot return a function or an array. However, you can define a function that returns a pointer to a function or a pointer to an array. The parameter declarations are contained in a comma-separated list of declarations of the function's parameters. If the function has no parameters, this list is either empty or contains merely the word void. The type of a function specifies not only its return type, but also the types of all its parameters. Example 7-1 is a simple function to calculate the volume of a cylinder. Example 7-1. Function cylinderVolume( )// The cylinderVolume( ) function calculates the volume of a cylinder. // Arguments: Radius of the base circle; height of the cylinder. // Return value: Volume of the cylinder. extern double cylinderVolume( double r, double h ) { const double pi = 3.1415926536; // Pi is constant return pi * r * r * h; } This function has the name cylinderVolume, and has two parameters, r and h, both with type double. It returns a value with the type double. 7.1.1. Functions and Storage Class SpecifiersThe function in Example 7-1 is declared with the storage class specifier extern. This is not strictly necessary, since extern is the default storage class for functions. An ordinary function definition that does not contain a static or inline specifier can be placed in any source file of a program. Such a function is available in all of the program's source files, because its name is an external identifier (or in strict terms, an identifier with external linkage: see "Linkage of Identifiers" in Chapter 11). You merely have to declare the function before its first use in a given translation unit (see the section "Function Declarations," later in this chapter). Furthermore, you can arrange functions in any order you wish within a source file. The only restriction is that you cannot define one function within another. C does not allow you to define "local functions" in this way. You can hide a function from other source files. If you declare a function as static, its name identifies it only within the source file containing the function definition. Because the name of a static function is not an external identifier, you cannot use it in other source files. If you try to call such a function by its name in another source file, the linker will issue an error message, or the function call might refer to a different function with the same name elsewhere in the program. The function printArray( ) in Example 7-2 might well be defined using static because it is a special-purpose helper function, providing formatted output of an array of float variables. Example 7-2. Function printArray( )// The static function printArray( ) prints the elements of an array // of float to standard output, using printf( ) to format them. // Arguments: An array of float, and its length. // Return value: None. static void printArray( const float array[ ], int n ) { for ( int i=0; i < n; ++i ) { printf( "%12.2f", array[i] ); // Field width: 12; decimal places: 2. if ( i % 5 == 4 ) putchar( '\n' ); // New line after every 5 numbers. } if ( n % 5 != 0 ) putchar( '\n' ); // New line at the end of the output. } If your program contains a call to the printArray( ) function before its definition, you must first declare it using the static keyword: static void printArray( const float [ ], int ); int main( ) { float farray[123]; /* ... */ printArray( farray, 123 ); /* ... */ } 7.1.2. K&R-Style Function DefinitionsIn the early Kernighan-Ritchie standard, the names of function parameters were separated from their type declarations. Function declarators contained only the names of the parameters, which were then declared by type between the function declarator and the function block. For example, the cylinderVolume( ) function from Example 7-1 would have been written as follows: double cylinderVolume( r, h ) double r, h; // Parameter declarations. { const double pi = 3.1415926536; // Pi is constant. return pi * r * r * h; } This notation, called a "K&R-style " or "old-style" function definition , is deprecated, although compilers still support it. In new C source code, use only the prototype notation for function definitions, as shown in Example 7-1. 7.1.3. Function ParametersThe parameters of a function are ordinary local variables. The program creates them, and initializes them with the values of the corresponding arguments, when a function call occurs. Their scope is the function block. A function can change the value of a parameter without affecting the value of the argument in the context of the function call. In Example 7-3, the factorial( ) function, which computes the factorial of a whole number, modifies its parameter n in the process. Example 7-3. Function factorial( )// factorial( ) calculates n!, the factorial of a non-negative number n. // For n > 0, n! is the product of all integers from 1 to n inclusive. // 0! equals 1. // Argument: A whole number, with type unsigned int. // Return value: The factorial of the argument, with type long double. long double factorial( register unsigned int n ) { long double f = 1; while ( n > 1 ) f *= n--; return f; } Although the factorial of an integer is always an integer, the function uses the type long double in order to accommodate very large results. As Example 7-3 illustrates, you can use the storage class specifier register in declaring function parameters. The register specifier is a request to the compiler to make a variable as quickly accessible as possible. No other storage class specifiers are permitted on function parameters. 7.1.4. Arrays as Function ParametersIf you need to pass an array as an argument to a function, you would generally declare the corresponding parameter in the following form: type name[ ] Because array names are automatically converted to pointers when you use them as function arguments, this statement is equivalent to the declaration: type *name When you use the array notation in declaring function parameters, any constant expression between the brackets ([ ]) is ignored. In the function block, the parameter name is a pointer variable, and can be modified. Thus the function addArray( ) in Example 7-4 modifies its first two parameters as it adds pairs of elements in two arrays. Example 7-4. Function addArray( )// addArray( ) adds each element of the second array to the // corresponding element of the first (i.e., "array1 += array2", so to speak). // Arguments: Two arrays of float and their common length. // Return value: None. void addArray( register float a1[ ], register const float a2[ ], int len ) { register float *end = a1 + len; for ( ; a1 < end; ++a1, ++a2 ) *a1 += *a2; } An equivalent definition of the addArray( ) function, using a different notation for the array parameters, would be: void addArray( register float *a1, register const float *a2, int len ) { /* Function body as earlier. */ } An advantage of declaring the parameters with brackets ([ ]) is that human readers immediately recognize that the function treats the arguments as pointers to an array, and not just to an individual float variable. But the array-style notation also has two peculiarities in parameter declarations :
Here is an example that combines both of these possibilities: int func( long array[const static 5] ) { /* ... */ } In the function defined here, the parameter array is a constant pointer to long, and so cannot be modified. It points to the first of at least five array elements. C99 also lets you declare array parameters as variable-length arrays (see Chapter 8). To do so, place a nonconstant integer expression with a positive value between the square brackets. In this case, the array parameter is still a pointer to the first array element. The difference is that the array elements themselves can also have a variable length. In Example 7-5, the maximum( ) function's third parameter is a two-dimensional array of variable dimensions. Example 7-5. Function maximum( )// The function maximum( ) obtains the greatest value in a // two-dimensional matrix of double values. // Arguments: The number of rows, the number of columns, and the matrix. // Return value: The value of the greatest element. double maximum( int nrows, int ncols, double matrix[nrows][ncols] ) { double max = matrix[0][0]; for ( int r = 0; r < nrows; ++r ) for ( int c = 0; c < ncols; ++c ) if ( max < matrix[r][c] ) max = matrix[r][c]; return max; } The parameter matrix is a pointer to an array with ncols elements. 7.1.5. The main( ) FunctionC makes a distinction between two possible execution environments:
In a freestanding environment , the name and type of the first function invoked when the program starts is determined by the given implementation. Unless you program embedded systems, your C programs generally run in a hosted environment . A program compiled for a hosted environment must define a function with the name main, which is the first function invoked on program start. You can define the main( ) function in one of the following two forms:
These two approaches conform to the 1989 and 1999 C standards. In addition, many C implementations support a third, nonstandard syntax as well:
In all cases, the main( ) function returns its final status to the operating system as an integer. A return value of 0 or EXIT_SUCCESS indicates that the program was successful; any nonzero return value, and in particular the value of EXIT_FAILURE, indicates that the program failed in some way. The constants EXIT_SUCCESS and EXIT_FAILURE are defined in the header file stdlib.h. The function block of main( ) need not contain a return statement. If the program flow reaches the closing brace } of main( )'s function block, the status value returned to the execution environment is 0. Ending the main( ) function is equivalent to calling the standard library function exit( ), whose argument becomes the return value of main( ). The parameters argc and argv (which you may give other names if you wish) represent your program's command-line arguments. This is how they work:
The sample program in Example 7-6, args.c, prints its own name and command-line arguments as received from the operating system. Example 7-6. The command line#include <stdio.h> int main( int argc, char *argv[ ] ) { if ( argc == 0 ) puts( "No command line available." ); else { // Print the name of the program. printf( "The program now running: %s\n", argv[0] ); if ( argc == 1 ) puts( "No arguments received on the command line." ); else { puts( "The command line arguments:" ); for ( int i = 1; i < argc; ++i ) // Print each argument on // a separate line. puts( argv[i] ); } } } Suppose we run the program on a Unix system by entering the following command line: $ ./args one two "and three" The output is then as follows: The program now running: ./args The command line arguments: one two and three |