Now we return to the Newton-Raphson code and discuss in more detail the mechanics of passing subprogram arguments.
We start by examining pointer arguments. Suppose we call the
subprogram with
f_and_df(x, &f, &df);
and the subprogram is defined as
void f_and_df(double x, double *fp, double *dfdxp){
...
}
When control is passed from the calling program to the subprogram it
is as though the following declarations with initialization take
place:
double x(subprogram) = x(calling program);
double *fp(subprogram) = &f(calling program);
double *dfdxp(subprogram) = &dfdx(calling program);
(The comments in parentheses here are not C++ code!) So the
declarations of the formal arguments x, fp, and dfdxp are turned into a combination declaration plus initialization.
There is an important difference between these implied
declaration/initializations and the ones we discussed before. The
formal subprogram arguments do not have permanent memory allocated to
them. Instead, temporary space called a "stack" is created for them.
Figure
shows a possible storage for the variables in
memory and the contents of the stack for the case that the initial
value of x is
and zero for f and
dfdx. After the stack is set up, control is passed to
the subprogram, which works only with the contents of the stack. When
control is returned to the calling program, the stack is immediately
discarded. Please note that the contents are not copied back.
The temporary stack makes it more challenging to get output values
from the subprogram. To see this, suppose the subprogram tries to
change the value of x with the assignment
x = 305;
That would attempt to make x both an input and output
value. The assignment only changes the number for x in
the stack. The original value in the calling program memory is
untouched. And when control is returned to the calling program the
changed stack is discarded. The original value of x is
still unchanged.
Now we can understand the rationale behind pointers. If we hand the
address of the variable to the subprogram, the subprogram can use it
to access the variable itself. With the assignment
*fp = 4*x - cos(x); // the * dereferences the pointer
the subprogram looks up the address specified by fp (22
in the above example) and puts the answer there. Since fp points to f the value of f is
changed.
References make the job easier. The subprogram definition
void f_and_df(double x, double &fr, double &dfdxr)
instructs the compiler to treat the second and third arguments as
though they were the same as the variables in the calling statement.
The calling statement
f_and_df(x, f, dfdx);
is treated as a declaration with initialization like this:
double x (subprogram) = x (calling program);
double &fr (subprogram) = f (calling program);
double &dfdxr (subprogram) = dfdx (calling program);
So just as with the reference variable g in the simple
example above, when we assign a value to fr it is really
f that is being changed.
While we have concentrated on using pointers and references only for
output arguments so far, they can also be used as input and for
arguments that are dual purpose: input and output. For example, to
use pointers for all the arguments we would define the function this
way:
// The definition of the subprogram
void f_and_df(double *xp, double *fp, double *dfdxp){
*fp = 4*(*xp) - cos(*xp); // the * dereferences the pointer
dfdxp = 4 + sin(*xp);
}
and call it with ampersands on all arguments f_and_df(&x,&f,&df);. Notice that it is necessary to
dereference the pointer to x in the expressions on the
rhs. For readability and to avoid ambiguities, it is a good idea to
put parentheses around the dereferenced values in the expressions, as
we have done.
Later we will see how to pass arrays as arguments.