|
|
HP C
|
Previous | Contents | Index |
A C program is a collection of user-defined and system-defined functions. Functions provide a convenient way to break large computing tasks into smaller ones, which helps in designing modular programs that are easier to understand and maintain. A function contains zero or more statements to be executed when it is called, can be passed zero or more arguments, and can return a value.
This chapter discusses the following information about C functions:
A function call is a primary expression, usually a function identifier followed by parentheses, that is used to invoke a function. The parentheses contain a (possibly empty) comma-separated list of expressions that are the arguments to the function. The following is an example of a call to the function power, assuming this function is appropriately defined:
main() { . . . y = power(x,n); /* function call */ } |
See Section 6.3.2 for more information on function calls.
A function has the derived type "function returning type". The type can be any data type except array types or function types, although pointers to arrays and functions can be returned. If the function returns no value, its type is "function returning void", sometimes called a void function. A void function in C is equivalent to a procedure in Pascal or a subroutine in FORTRAN. A non-void function in C is equivalent to a function in these other languages.
Functions can be introduced into a program in one of two ways:
int power(int base, int exp) { int n=1; if (exp < 0) { printf ("Error: Cannot handle negative exponent\n"); return -1; } for ( ; exp; exp--) n = base * n; return n; } |
main() { int power(int base, int exp); /* function declaration */ int x, n, y; . . . y = power(x,n); /* function call */ } |
A function definition includes the code for the function. Function definitions can appear in any order, and in one source file or several, although a function cannot be split between files. Function definitions cannot be nested.
A function definition has the following syntax:
function-definition:
declaration-specifiersopt declarator declaration-listopt compound-statement |
declaration-specifiers
The declaration-specifiers (storage-class-specifier, type-qualifier, and type-specifier) can be listed in any order. All are optional.By default, the storage-class-specifier is extern. The static specifier is also allowed. See Section 2.10 for more information on storage-class specifiers.
ANSI allows the type-qualifier to be const or volatile, but either qualifier applied to a function return type is meaningless, because functions can only return rvalues and the type qualifiers apply only to lvalues.
The type-specifier is the data type of the value returned by the function. If no return type is specified, the function is declared to return a value of type int. A function can return a value of any type except "array of type" or "function returning type". Pointers to arrays and functions can be returned. The value returned, if any, is specified by an expression in a return statement. Executing a return statement terminates function execution and returns control to the calling function. For functions that return a value, any expression with a type compatible with the function's return type can follow return using the following format:
- return expression;
If necessary, the expression is converted to the return type of the function. Note that the value returned by a function is not an lvalue. A function call, therefore, cannot constitute the left side of an assignment operator.
The following example defines a function returning a character:
char letter(char param1) { . . . return param1; }The calling function can ignore the returned value. If no expression is specified after return, or if a function terminates by encountering the right brace, then the return value of the function is undefined. No value is returned in the case of a void function.
If a function does not return a value, or if the function is always called from within a context that does not require a value, a return type of void should be specified:
void message() { printf("This function has no return value."); return; }Specifying a return type of void in a function definition or declaration generates an error under the following conditions:
- If the function attempts to return a value, an error occurs at the offending return statement.
- If the void function is called in a context that requires a value, an error occurs at the function call site.
declarator
The declarator specifies the name of the function being declared. A declarator can be as simple as a single identifier, such as f1 in the following example:
int f1(char p2)In the following example, f1 is a "function returning int". A declarator can also be a more complex construct, as in the following example:
int (*(*fpapfi(int x))[5])(float)In this example, fpapfi is a "function (taking an int argument) returning a pointer to an array of five pointers to functions (taking a float argument) returning int". See Chapter 4 for information on specific declarator syntax.
The declarator (function) need not have been previously declared. If the function was previously declared, the parameter types and return type in the function definition must be identical to the previous function declaration.
The declarator can include a list of the function's parameters. In HP C, up to 253 parameters can be specified in a comma-separated list enclosed in parentheses. Each parameter has the auto storage class by default, although register is also allowed. There is no semicolon after the right parenthesis of the parameter list.
There are two methods of specifying function parameters:
- The new or prototype style, which includes a parameter type list. For example:
int f1(char a, int b) { function body }- The old style, which includes an identifier list; the parameter types are defined in a separate declaration-list within the function definition, before the left brace that begins the function body. For example:
int f1(a, b) char a; int b; { function body }
Any undeclared parameters are assumed to be of type int.A function definition with no parameters is defined with an empty parameter list. An empty parameter list is specified in either of two ways:
- Using the keyword void if the prototype style is used. For example:
char msg(void) { return 'a'; }- Using empty parentheses if the old style is used. For example:
char msg() { return 'a'; }A function defined using the prototype style establishes a prototype for that function. The prototype must agree with any preceding or following declarations of the same function.
A function defined using the old style does not establish a prototype, but if a prototype exists because of a previous declaration for that function, the parameter declarations in the definition must exactly match those in the prototype after the default argument promotions are applied to the parameters in the definition.
Avoid mixing old style and prototype style declarations and definition for a given function. It is allowed but not recommended.
See Section 5.6 for more information on function parameters and arguments. See Section 5.5 for more information on function prototypes.
compound-statement
The compound-statement is the group of declarations and statements surrounded by braces in a function or loop body. This compound statement is also called the function body. It begins with a left brace ({) and ends with a right brace (}), with any valid C declarations and statements in between. One or more return statements can be included, but they are not required.
A function can be called without declaring it if the function's return value is int (although this practice is not recommended due to the loss of type-checking capability; all functions should be declared). If the return value is anything else, and if the function definition is located after the calling function in the source code, the function must be declared before calling it. For example:
char lower(int c); /* Function declaration */ caller() /* Calling function */ { int c; char c_out; . . . c_out = lower(c); /* Function call */ } char lower(int c_up) /* Function definition */ { . . . } |
If the function definition for lower was located before the function caller in the source code, lower would not have to be declared again before calling it. In that case, the function definition would serve as its own declaration and would be in scope for any function calls from within all subsequently defined functions in the same source file.
Note that both the function definition and function declaration for lower are in the prototype style. Although C supports the old style of function declaration in which the parameter types are not specified in the function declarator, it is good programming practice to use prototype declarations for all user-defined functions in your program, and to place the prototypes before the first use of the function. Also note that it is valid for the parameter identifier in the function declaration to be different from the parameter identifier in the function definition.
In a function declaration, the void keyword should be used to specify an empty argument list. For example:
char function_name(void); |
As with function definitions, the void keyword can also be used in function declarations to specify the return value type for functions that do not return a value. For example:
main() { void function_name( ); . . . } void function_name( ) { } |
A function prototype is a function declaration that specifies the data types of its arguments in the parameter list. The compiler uses the information in a function prototype to ensure that the corresponding function definition and all corresponding function declarations and calls within the scope of the prototype contain the correct number of arguments or parameters, and that each argument or parameter is of the correct data type.
Prototypes are syntactically distinguished from the old style of function declaration. The two styles can be mixed for any single function, but this is not recommended. The following is a comparison of the old and the prototype styles of declaration:
The HP C compiler will warn about old-style function declarations only in strict ANSI standard mode, or when the check compiler option is specified. |
Prototype style:
A function prototype has the following syntax:
function-prototype-declaration:
|
The declarator includes a parameter type list, which specifies the types of, and can declare identifiers for, the parameters of the function.
A parameter type list can consist of a single parameter of type void to specify that the function has no parameters.
A parameter type list can contain a member that is a variable-length array, specified by the [*] notation.
In its simplest form, a function prototype declaration might have the following format:
storage_classopt return_typeopt function_name ( type1 parameter1, ..., typen parametern ); |
Consider the following function definition:
char function_name( int lower, int *upper, char (*func)(), double y ) { } |
The corresponding prototype declaration for this function is:
char function_name( int lower, int *upper, char (*func)(), double y ); |
A prototype is identical to the header of its corresponding function definition specified in the prototype style, with the addition of a terminating semicolon (;) or comma (,), as appropriate (depending on whether the prototype is declared alone or in a multiple declaration).
Function prototypes need not use the same parameter identifiers as in the corresponding function definition because identifiers in a prototype have scope only within the identifier list. Moreover, the identifiers themselves need not be specified in the prototype declaration; only the types are required.
For example, the following prototype declarations are equivalent:
char function_name( int lower, int *upper, char (*func)(), double y ); char function_name( int a, int *b, char (*c)(), double d ); char function_name( int, int *, char (*)(), double ); |
Though not required, identifiers should be included in prototypes to improve program clarity and increase the type-checking capability of the compiler.
Variable-length argument lists are specified in function prototypes with ellipses. At least one parameter must precede the ellipses. For example:
char function_name( int lower, ... ); |
Data-type specifications cannot be omitted from a function prototype.
The C99 standard permits the keyword static to be used within the outermost array bound of a formal parameter in a prototype function declaration. The effect is to assert to the compiler that at each call to the function, the corresponding actual argument will provide access to at least as many array elements as specified in the declared array bound. Consider the following two function definitions:
void foo(int a[1000]){ ... } void bar(int b[static 1000]) { ...} |
The declaration of foo is absolutely equivalent to one that declares a to be int *. When compiling the body of foo, the compiler has no information about how many array elements might exist. The declaration of bar differs in that the compiler can assume that at least 1000 array elements exist and may be safely accessed. The intent is to provide a hint to the optimizer about what can be safely pre-fetched.
Prototypes must be placed appropriately in each compilation unit of a program. The position of the prototype determines its scope. A function prototype, like any function declaration, is considered within the scope of a corresponding function call only if the prototype is specified within the same block as the function call, any enclosing block, or at the outermost level of the source file. The compiler checks all function definitions, declarations, and calls from the position of the prototype to the end of its scope. If you misplace the prototype so that a function definition, declaration, or call occurs outside the scope of the prototype, any calls to that function behave as if there were no prototype.
The syntax of the function prototype is designed so that you can extract the function header of each of your function definitions, add a semicolon (;), place the prototypes in a header, and include that header at the top of each compilation unit in your program. In this way, function prototypes are declared to be external, extending the scope of the prototype throughout the entire compilation unit. To use prototype checking for C library function calls, place the #include preprocessor directives for the .h files appropriate for the library functions used in the program.
It is an error if the number of arguments in a function definition, declaration, or call does not match the prototype.
If the data type of an argument in a function call does not match the corresponding type in the function prototype, the compiler tries to perform conversions. If the mismatched argument is assignment-compatible with the prototype parameter, the compiler converts the argument to the data type specified in the prototype, according to the argument conversion rules (see Section 5.6.1).
If the mismatched argument is not assignment-compatible with the prototype parameter, an error message is issued.
C functions exchange information by means of parameters and arguments. The term parameter refers to any declaration within the parentheses following the function name in a function declaration or definition; the term argument refers to any expression within the parentheses of a function call.
The following rules apply to parameters and arguments of C functions:
Previous | Next | Contents | Index |
|