Previous Table of Contents Next


3.5.3. Calling Functions

A function may be called anywhere in any expression (with the exception of constant expressions). A function call consists of the name of the function followed by a pair of parentheses containing the comma-separated list of actual arguments to be passed to the function. (The parentheses are required even if there are no arguments.) At some point during the evaluation of the expression (unless evaluation has been short-circuited by the &&, ||, or ?: operators), the expressions denoting the arguments are themselves evaluated, the resulting values passed to the function, and the function’s return value propagated to the rest of the expression, in the natural and obvious ways. Simple examples of function calls are

c = sqrt(a * a + b * b);

and

x = gcd(5535, 8241);

C uses pass by value function-call semantics. With one (apparent) exception, the function receives copies of the values of its arguments, and these copies act essentially as local variables within the function, referred to by the names given in the parameter list of the function’s definition. This means that a function cannot alter the values of ordinary variables passed to it as arguments. For example, if we were to write

int x = 20, y = 55;
int z = gcd(x, y);

the variables x and y would not be affected by the call to gcd, even though the implementation of gcd in section 3.5.1 assigned new values to its parameters a and b. (It is perfectly legal for a function to modify its parameters, as that sample gcd function does, if it is useful to the function to do so, and as long as the modified value is not expected to be propagated back to the caller.)

The one exception to the preceding rule concerns arrays. When a function is called with an array as an argument, a copy of the entire array is not made. Instead, the called function receives a reference to the caller’s array, and any modifications made to the array by the called function do affect the caller’s array. (As described in section 3.6.5, the “reference” takes the form of a pointer.)

The details of C’s function calling mechanism differ depending on the form (if any) of declaration that has been seen for a particular function. Broken down to finest detail, there are four cases:

  No declaration for the function is in scope. The function is assumed to return int and to have parameters implied by the arguments it is actually called with. Several default argument promotions are applied to the actual arguments: Arguments of type char and short int (or their unsigned variants) are promoted to int or unsigned int, and arguments of type float are promoted to double. (Because they can undergo these promotions, the three types char, short int, and float are often referred to as “narrow” types.) If the arguments actually passed do not turn out to be compatible with the number or type of the parameters expected by the function, or if the actual return type is not int, the results are undefined.
  A nonprototyped declaration for the function is in scope. The declaration gives the return type of the function but not the expected types of its arguments. As for the first case, the function is assumed to have parameters implied by the actual arguments. The same default argument promotions are performed.
  A prototype declaration for the function is in scope, specifying a fixed number of arguments. Each argument is converted, as if by assignment, to the type of the corresponding parameter. If a conversion is impossible, the compiler reports an error. If there are more or fewer arguments than are specified in the parameter list, the compiler reports an error. The default argument promotions are not performed; arguments may be passed as char, short int, or float if their corresponding parameters have those types. The prototype also gives the return type of the function.
  A prototype declaration for the function is in scope, specifying a variable number of arguments. For the “fixed” arguments, declared explicitly in the prototype, the arguments are converted to the expected types and passed exactly as for the third case. If any are incompatible, or if there are fewer arguments than expected, the compiler reports an error. The additional arguments (if present) are promoted and passed according to the default argument promotions of the first and second cases.

These four cases and their defined handling lead to several rules and guidelines:

  It is safest to use the modern, ANSI-style function definition syntax, and to ensure that external function prototype declarations are in scope for all function calls. Most compilers can now be set to warn about functions called without prototypes in scope.
  Functions that accept “narrow” parameters must be called with prototypes in scope (and must be defined using the ANSI syntax).
  It is possible to mix old-style function definitions with ANSI prototype declarations, but doing so results in several additional caveats. “Narrow” parameters must either be avoided or carefully adjusted in prototypes, and variable-length argument lists may not be used or must be written entirely in the ANSI syntax. Many programmers recommend avoiding narrow parameters in any case, or recommend against mixing definition and declaration styles.
  Functions accepting a variable number of arguments must be defined using the ANSI syntax and must always be called with external function prototypes in scope.
  The arguments in variable-length argument lists are always promoted according to the default argument promotions; they are never passed as char, short int, or float.


Previous Table of Contents Next