ECE-1021

Argument Passing

(Last Mod: 27 November 2010 21:38:41 )

ECE-1021 Home



Objectives


Argument Passing Overview

Consider the function Fred() defined as follows:

int Fred(int k, int *ptr)

(

    int product;

    product = k * (*ptr);

    return product;

}

 

int main(void)

{

    int i, j, m;

    /*...*/

    i = 10;

    j = 20;

    m = Fred( 5*i, &j);

    /*...*/

    return 0;

}

The function main() calls the function Fred() and passes it two arguments. Each argument is an expression that is evaluated and the resulting value is passed to the function. If an argument is a single variable or constant - which is still an expression - then it simply evaluates to the value stored in the variable or the value of the constant.

The values of the two arguments that are passed by the call to Fred() are "caught" by the two local variables that are in Fred()'s parameter list. In this case, the first argument evaluates to 50 and the second argument evaluates to the memory address at which the variable j is stored (the & operator, when it precedes a single variable identifier, is the "address operator"). For illustration sake, let's assume that the variable j is stored at memory location 1000. Hence the local variable k within Fred() is initialized to 50 and the local variable ptr is initialized to 1000.

Although we will shortly see that we refer to the first argument as being passed by value and the second argument as being passed by reference, we can see that this is really a matter of semantics. In both cases an expression was evaluated and the resulting value was copied into the corresponding variable in the function's parameter list. Hence, in a very real sense, all arguments in C are passed by value.

The semantics come in to play because, from a logical standpoint, we don't think of passing the address of the variable j - even though that is exactly what we are doing. We generally think in terms of passing the information that is stored in j - which is not what we are actually doing. To solve this mental discrepancy we invent the concept of passing-by-reference whereby we console ourselves with the understanding that, by passing the address of the variable j we are, from a logical standpoint, passing a reference to where the variable j is stored so that the function can indirectly access the value stored in it rather that us passing that value directly.

In most instances, we can pass information to a function either way - either directly (pass-by-value) or indirectly (pass-by-reference). Depending on what our goal is, one method may be significantly more advantageous, it may be a toss-up, or one method may not be a viable option. But even if both methods are feasible, it is critical to understand that the person that writing the code that calls a given function has no choice in which method is used - that decision was made by the person who wrote the function being called.

Pass-by-Value

Pass by value means that we pass the actual value of a piece of data directly to the function. What actually happens is that the function makes a local copy of the value we pass it and uses that local copy. It can therefore modify that value at will and the data we actually passed it is safely protected. This is the preferred means of passing data to functions when it is practical to do it this way.

Pass-by-Reference

While passing by value protects our data from any actions of the function we called, there are two instances where it is not practical to utilize this protection. The first is we want or need the function to be able to change the value stored in our data object - not just some local copy of that object. The other is if the data object is large and would result in an excessive amount of processing overhead to make local copies each time it is passed. If either of these situations arise, we can pass the function a "reference" to the data instead of the data itself. A reference in this context is nothing more than the memory location, or address, where the actual data is stored.


Pointers - Part I

When we pass data by reference, we need to work with pointers - but a pointer is nothing more than a data type suitable for storing a memory address. It is very commonly nothing more than a long int.

Pointer Declaration

A variable that is used to store an address value is called a pointer variable. The type of pointer is the same as the type of data stored at the address in question. When we declare a pointer variable, we must provide both pieces of information - the fact that it is a pointer and the type of data it points to. We accomplish the first by preceding the variable name with an asterisk - this identifies the variable as a pointer. The type of pointer is indicated in exactly the same fashion as any other variable of that same type. Hence the declaration:

int m, *ptr;

Declares two variables. The variable m is of type "int", meaning that the value stored in it is of type int. The variable ptr is of type "int pointer", also expressed as "pointer to int". This means that the value stored in it is a memory address and the value stored at the indicated address is of type int.

Pointer Dereferencing

Referring back to the example program from the top of this module, we said that Fred() was called with two arguments that evaluated to 50 and 1000. These values were stored in the variables k and ptr respectively. The purpose of Fred() is, presumably, to return the product of the two pieces of information passed to it - one of which was passed by value and the other by reference. So the first piece of information is 5*i, or 50, and the second piece of information is 20, that value stored in j. Hence we expect this function to return a value of 100.

If we simply multiply the two values received by the function we would have:

product = k * ptr;

which would yield a value of 50,000. Clearly not what we want. What we need is a way of first using the value stored in ptr to go out to that memory address and retrieve the value stored there. To do this we use the "indirection operator", which is the asterisk. So the expression *ptr evaluates to the value stored at the address contained in the variable ptr. Hence we need to use:

product = k * *ptr;

Don't confuse the use of the asterisk as the indirection operator with the use of that same character to represent a completely different operator - namely the one for multiplication. The context in which it is used permits the compiler to distinguish between the two. But, even if the compiler can keep track of the difference, it's easier for humans to keep track if we are pretty liberal with the use of parentheses:

product = k * (*ptr);