Next: Recursion Up: Procedures Previous: A brief digression

Returning values via arguments

As only argument values are passed in, it is not possible to pass values back through the arguments. However, a similar effect can be achieved. C allows you to obtain the actual store address of a variable. The ampersand operator (``address of'') is used for this.

Thus:

   int speed;

means that speed is an integer variable.

   &speed

is the address at which speed is stored.

Recall that computer memory can be thought of as a sequentially numbered set of ``pigeon-holes'', starting at number 0 and working upwards to some machine-dependent upper limit. This number is the address of the P/H, the address of the storage location.

Each P/H can store a value. In our case &speed is one of the addresses and the value of speed is stored there. In C, it is most important to get clear this distinction between the value that a variable has and the address where the value is stored.

Confusion arises because an address is just a value too. So, we can pass it into a function as an argument: in effect we are telling the function where a particular variable is stored, rather than what is stored there.

That being so, the function can look in that address to see what is held there and, more importantly, can change what is stored in there. To do this in C we need one more operator which, given an address, lets us get at what is stored there.

C uses the * operator for this. (Operator overload: multiply uses this symbol too) We need an example to see how this works. Suppose we have

   int speed;

Then &speed is its address. *(&speed) is the value held in speed, an integer in this case.

At first, this may look fruitless: *(&speed) is the same as speed. In fact this generalises in a powerful way, as we will see later in the course, and is one of the features of C which distinguishes it clearly as a system programming language giving you maximum flexibility. For now we note that we can write functions as follows.

/* Doubles its integer argument */
void times_two
   (
   int *number_address   /* pointer to a number */
   )
{
   *number_address= (*number_address)*2;
}

Note the new syntax: int *number_address;

This says that the argument number_address is in fact the address of an int. number_address is of type int *, a pointer to an integer.

[Note that this does not mean that number_address is of type int.]

To get at the value stored there I need to use *number_address.

There is a better way to think of this: number_address is a pointer to an int; ``*'' says follow the pointer and look inside what you find. One reason this is better is that that the concept of ``address'' can vary in a machine-dependent way, depending on what type of object we are examining, so it is better to think of pointers.

Let's see how to put this together in a complete program.

int main()
{
int my_number;

my_number= 12;
printf("Original number is %d ",my_number);
times_two(&my_number);
printf("which, after doubling, is %d\n",my_number);
}

/* Doubles the integer argument pointed to */
times_two
   (
   int *number_address   /* pointer to a number */
   )
{
   *number_address= (*number_address)*2;
}

Notes

1. It is not now sensible to write

   times_two(26)

or

   times_two(a/b)

2. Note the symmetry between passing in an address of an integer at the call (using &my_number) and defining the argument as the address of an integer (with int *number).

3. We are still passing only a value into the function: it is now the value of the address, rather than the value stored there, but it is still a value.

4. It is quite common to use the function name to return status information saying how the function performed, even if arguments carry the ``proper'' data back and forth. As an example, you might write a function to sort some data, but use the function name to tell you whether the data was already ordered: using true/false as the return value. This would allow you to write:

   if ( sort(...) )
      printf("Data now sorted\n");
   else
      printf("Data is already sorted\n");

Note that the sort is performed by the invocation, within the if statement. Sort must return an integer 0 (False) or non-zero (True) as appropriate. This is an example of an expression having a value which is put to use: all C expressions have a value but often these are thrown away.

5. An address can be thought of loosely as an (unsigned) integer value (but beware of trying to use arbitrary integers as addresses). It is quite possible to compute with it and even to print it out. Try the following program.

int main()
{
int expt;

expt= 199;

printf("The value %d is stored in address %d\n", expt, &expt);
}

The value printed could be negative, even though addresses are non-negative: why is this?

6. It is possible to pass arguments to main: we need to know about arrays to do this properly, so we will return to this later.



Next: Recursion Up: Procedures Previous: A brief digression


maspjw@
Tue Sep 27 15:29:34 BST 1994