Previous: 3.2.1 Passing Data to and from Functions
Up: 3.2 Defining Functions
Previous Page: 3.2.1 Passing Data to and from Functions
Next Page: 3.3 Coding Programs for Readability

3.2.2 Call by Value and Local Variables

This section reviews and formalizes several features of variables that we have already encountered. We know that direct access of objects is performed by using variable names in expressions. The use of a variable on the left side of an assignment operator stores a new value in that object; the use of a variable anywhere else retrieves the value of the object. Objects defined in one function are not directly accessible to other functions. A calling function passes values of arguments to a called function. Only the values of these arguments, and NOT the arguments themselves, are available to the called function. The values of the arguments are stored in the parameters, and only the called function has access to these parameters. When called functions have access only to argument values, and not to arguments themselves, the function calls are termed call by value. In C, all function calls are call by value. It is impossible for a called function to have direct access to an object defined in the calling function. Let us examine the implications. Consider a program that uses a function to increment the value of an argument.

/*   File: incr.c                                 Program Trace
        Program demonstrates call by value.               x
   */
   #include <stdio.h>
   main()
   {    int x;                                            ??
        int incr(int n);

printf("***Call by Value***\n"); x = 7; 7 printf("Original value of x is %d\n", x); 7 printf("Value of incr(x) is %d\n", incr(x)); 7

printf("The value of x is %d\n", x); 7 }

/* Function increments n */ n int incr(int n) 7 { n = n + 1; 8 return n; 8 }

Compiling and executing this programs gives the following sample session:

The program trace shows that x in main() is assigned a value of 7 prior to a function call to incr() which increments its parameter to 8 and returns that value. After the function call, the value of x in main() is still 7, unchanged because only the value of x is passed to incr(). It was the cell, n, in incr() that was incremented as seen in Figure 3.7

We see that a called function cannot directly change the value of an object defined in the calling function. This is true even if the formal parameter in incr() were called x. Formal parameters represent new and distinct objects unrelated to any other objects defined elsewhere.

The variables declared at the beginning of a block (e.g. a function body) have all been of a storage class called automatic. This means that these variables are automatically created and destroyed each time the function is executed. When the execution of a function begins, the variables declared at the beginning of the function block as well as the formal parameters are created, i.e. memory cells for these variable names are allocated. When the execution of a function is completed (e.g. when a return statement is executed), the memory allocated for these variables is freed, i.e. these variables and their values no longer exist.

Automatic variables can be defined at the beginning of any block within the primary function block and exist only in the block in which they are defined. Memory for automatic variables declared in a block is allocated when the block is entered, and freed when the block is exited.

The scope of a variable is that part of the program where the variable is visible, i.e. where the variable can be accessed directly by name. The scope of automatic variables is local to the block in which they are defined as well as any blocks nested within it. Automatic variables are frequently referred to as local variables, since their scope is local.

A variable of automatic storage class can be explicitly defined in a declaration by preceding it with the keyword auto. Thus, the following declarations declare automatic variables:

auto int x, y;
     auto float r;
If no storage class is specified in a declaration, automatic storage class is assumed by default. In all of our programs, so far, declarations have been for automatic variables by default. In general, most variables used in programs are automatic, and the default declaration without the keyword auto is a standard practice. Other storage classes will be discussed in Chapter . Until then, we will use only automatic variables.

As we stated before, a declaration only allocates a memory cell and associates the name with the cell; the value in that cell is, in general, unknown. However, it is possible to specify initial values of automatic variables in the declaration statements. Examples include:

int x = 5 * 2;
     int y = isquare(2 * x);
     float z = 2.8;
The first declaration initializes x to 10, and the second initializes y to the value returned by the function call isquare(2 * x). If the function isquare() returns the square of its argument, then y in this case, is initialized to 400, i.e. the square of 2 * x. Finally, the last declaration initializes the variable z to the value 2.8.

The syntax for a declaration statement with initialization is:

The declaration allocates memory for variables named <identifier> of a type indicated by , and initializes the variable to the value of the initializer expression, <init-expr>. The initializer expression can be any C expression including function calls.

Consider the following example in which automatic variables are declared in nested blocks:

/*   File: auto.c
     Program shows declarations of automatic variables in nested
     blocks. Scope of automatic variables is the block in which they
     are defined.
*/
main()
{                                  /* outer block */
     auto int x = 10, z = 15;      /* x and z are allocated and initialized */

printf("***Automatic Variables and Scope***\n\n"); { /* inner block */ int x = 20, y = 30; /* new variables x and y are allocated */ /* only the new x can be accessed */ printf("In the inner block: \n"); printf("x = %d, y = %d, z = %d\n", x, y, z); /* new x and y, and z are printed */ } /* new x and y are freed */ printf("In the outer block:\n"); printf("x = %d, z = %d\n", x, z); /* only the old x can be */ /* accessed in the outer block.*/ /* printf("y = %d\n", y); error: y is not visible here. */ }

The program contains an outer block, which is the function body for main(), and an inner block. The scope rules say that an inner block can access variables declared within it plus any variables declared in an enclosing block. However, if the same variable name is used in an inner and an outer block, the local variable in the inner block is accessed. The outer block cannot access variables defined in an inner block.

In the example, variables x and z are declared in the outer block and assigned values. The outer block can access only these variables. Variables x and y are declared in the inner block and assigned values. The inner block can access the variables z, y, and that x which is defined in the inner block. As shown in a comment, if the outer block tried to access y, a compile time error would occur. This behavior can be seen in Figure 3.8.

The allocation of storage is shown when the program is executing within the inner block as can be seen by the nested box containing x and y. When this block is completed, the inner box, and all variables inside, is freed. A sample output of the program shows the results:

It is also possible to qualify an automatic variable as a constant using the keyword const. A const qualifier allows initialization of a variable but the variable may not be otherwise changed within the program. Here is an example:

const int x = 100;
In the above case, x is initialized to 100 and qualified as a constant. Its value may not be changed elsewhere in the program, e.g. in an assignment statement. Constant qualifiers are used to ensure that certain variable values are not altered by oversight.

Let us consider a somewhat more meaningful example that declares a variable in an inner block. The task is to swap values of two objects, x and y. We need a temporary variable to save one of the values; otherwise, assigning the value of to x would overwrite the original value of x. We can declare the temporary value in an inner block.

/*   File: swap.c
     This program swaps values of two objects. It defines and uses a
     temporary variable in an inner block.
*/
#include <stdio.h>
main()
{    int x = 10, y = 20;

printf("***Swap Values***\n\n"); printf("Original values: x = %d, y = %d\n", x, y); { int temp;

temp = x; x = y; y = temp; } printf("Swapped Values: x = %d, y = %d\n", x, y); }

Here is the output of the program:
  • ***Swap Values***
  • Original values: x = 10, y = 20
  • Swapped Values: x = 20, y = 10

Defining variables in blocks other than a primary function block is not recommended unless there are good reasons for it. In the above example, a temporary variable is declared closest to its use and has no logical role in the rest of the program. When a function uses many variables, declaring variables closest to their use may make it easier to understand the program behavior. For the most part, we will declare all variables at the beginning of primary function blocks.

The formal parameters of a function are also variables that are automatically allocated during a function call, and into which the argument values are passed. Their values, just like those of any other variables, may be changed in the function. The scope of the formal parameters is the body of the function, i.e. the scope is local to the function body.



Previous: 3.2.1 Passing Data to and from Functions
Up: 3.2 Defining Functions
Previous Page: 3.2.1 Passing Data to and from Functions
Next Page: 3.3 Coding Programs for Readability

tep@wiliki.eng.hawaii.edu
Wed Aug 17 08:21:42 HST 1994