You may
also want to read the reference document on Addresses and Pointers
which
you can also find on the website as Lecture 7 reference material.
Go directly to Lecture 7 Part B (re Swap Function)
1. Why is modular programming important?
Computer people (ah, yes, we are people) tend to work on large, complex problems and they often work in groups.
Research in Psychology and Education has shown that we perform tasks and learn better in chunks.
No matter what the task, most of us tend to work better if we can
· decompose that task into smaller parts,
· record the parts of the decomposition and how they relate to one another, and
· then work on each part separately, putting the pieces back together toward the end.
The basis for this research is the notion that most of us are able to focus on a small number of details at any one time. So, if our task decomposition is done in a reasonable way, the number of details we need to focus on at any one time is very limited, and the probability of success in our endeavor is greatly increased.
Of course, we all probably know some folks, some composers, systems designers, engineers, mathematicians, artists, and the like who are seemingly able to work out the details of large, complex problems in their heads. But most of cannot.
So we look for tools and methodologies with which we are comfortable to help us get a handle on the things we need to do.
This cannot only be helpful in our daily chores, but it has been shown to be very useful in computer programming.
We believe that tools such as behavior diagrams, structure charts, and data tables, can be useful in helping us decompose a large problem into smaller components or modules, while still retaining the overview needed in the end to put the smaller pieces together.
2. Functions and arguments
and their role in modular programming
In most computer programming languages, including C, C++, Pascal, FORTRAN, BASIC, and Java, functions provide one vehicle for implementing the separate components we design.
There are other vehicles as well, such as abstract data types, subroutines, procedures, and the like. But our focus in this course is on functions and how they communicate with one another i.e., through the use of input and output arguments (and, often, and single return value).
The details of argument passing are difficult to grasp at first. Unfortunately in the C language the use of output arguments (using the call by address mechanism) is particularly messy.
Most other language are not so messy in their handling of output arguments. Some, such as FORTRAN used only call by address argument passing, making things simpler to handle. Other languages, such as C++, Java, and Pascal required all functions to be used in a module to be declared before they could be called.
Although C now requires the use of prototypes (function declarations) before a function can be called, it originally had no such requirement. The output argument passing mechanism we will study for the C language is a hold over from the earlier days of C before prototypes.
Let us first review a function with which we are familiar.
// Function to draw a sequence of n characters, ch, on a single line
// (with no new line character)
// Returns 1 if n < 0. Otherwise returns 0.
int drawNchars
(int n; // IN:
Number of characters to be drawn
char ch) // IN:
The character to be drawn
If we call this function, say in main, using
k = drawNchars (8, *);
then our picture of memory at the time the call would look like this.
Main k = drawNchars (8, *); int drawNchars
(int n, // IN: number of characters
char ch) // IN:
actual character to

We note here that the arguments to this drawNchars function are call by value or input
arguments. Copies of the values of these arguments are made and these copies are passed
(by value) to the function.
Now lets examine a function with which we are NOT familiar.
Problem Write a function, separate, which, given a double precision value num,
determines and returns the sign of num, the integral (whole number) part of num, and the
fractional part of num. Also write a driver program to test the function.
How
do we approach this problem?
For the
first time, we have to write a function that returns more than one value to
the
caller.
To
return more than one value to a caller, we have to return at least some values
through output
arguments back to variables in the caller. So the function now
will
have to modify the contents of memory cells in the calling module.
This is
the first time we have had to ask a function to do this. Until now, a function
simply received
values from a caller and returned at most one value as its result.
To see how this is done, we begin by examining, once again, the relevant part of memory
allocated for any program that might call separate, and for the function separate itself, at
the time it is called.
Main void separate
(double num, // IN: value to be separated
char *signp, // OUT: sign of num
int *wholep, // OUT: whole part of
separate (a, &x, &y, &z); double *fractp) // OUT: fractional part of

![]()
![]()
Here we illustrate, on the left, a call to separate, and on the right, the function header.
We note that a, x, y, and z are the actual arguments used in the call to separate. Also,
num, signp, wholep, and fractp are the formal parameters in the function definition
that is, they are the names used represent these arguments inside the function. Remember,
except for the types and order of the actual arguments
and the formal parameters, main
and separate
are totally distinct functions and the names used in each are irrelevant to
the other.
Finally, we note that some strange new symbols are introduced into this picture symbols
that did not appear anywhere in the drawNchars picture shown earlier.
The
questions we now must answer are
We address these questions next.
What is the purpose of the
& (ampersand)?
In C, simple variables are, by default, passed by value. But we want separate to be able
to modify x, y, and z. It cannot do this UNLESS we pass it the addresses of x, y, and z
(as opposed to just the contents of x, y, z and). (Note that at the point of call to separate,
we dont even know the contents of x, y, or z.) If we are able to get the addresses of x, y,
and z stored in separate, then separate will be able to find x, y, and z, whenever it needs to
modify them.
Enter the ampersand, or address of operator (&)
.

&x (&y and &z) represent the address of x (y, and z)
The & is known as the address of (unary) operator.
The use of the ampersand (immediately preceding x, y, and z) in the call to separate
causes the compiler to make sure that x, y, and z are passed by address and not by value.
Thus, the addresses of x, y, and z (and not their values) end up being passed to separate
and stored in the corresponding memory cells, signp, wholep, and fractp, respectively.
As shown in the picture below, these three cells, signp, wholep, and fractp, now
contain pointers to (addresses of) the actual arguments x, y, and z. (It is for this reason
that we put a p after the names e.g., signp, to remind us that these three memory cells
actually contain pointers to another memory cell and not values that will be directly used.)
Main void separate
(double num, // IN: value to be separated
char *signp, // OUT: sign of num
int *wholep, // OUT: whole part of
separate (a, &x, &y, &z); double *fractp) // OUT: fractional part of

![]()
![]()
What is the purpose of the *
(asterisk)?
Getting the addresses of the actual arguments to be modified saved in the function (using
the ampersand), is just a part of the battle, however.
We next have to be certain that we properly use the addresses stored inside separate.
To consider how things might go WRONG here, we write a small piece of the function next.
void separate
(double num, // IN:
value to be separated
char *signp, // OUT: sign of num
int *wholep, // OUT: whole part of num
double *fractp) // OUT: fractional part of num
{
// Determine the sign of num and store the result back in caller
if (num < 0.0)
signp = -;
else
signp = +;
...
} // end of separate
The
way this is written causes lots of damage.
WHY?
Because: The sign character, - or + will be
stored in the memory cell signp, wiping out
the
address of x
which had been there. The is NOT what
we want. Rather, we want this
character
- or + to be stored in the memory cell pointed to by signp namely x. To do
this,
we must indicate that we want to dereference the contents
of signp, as
shown below.
x


Enter the asterisk (*) in this case, used as the pointer dereference operator.
void separate
(double num, // IN:
value to be separated
char *signp, // OUT: sign of num
int *wholep, // OUT: whole part of num
double *fractp) // OUT: fractional part of num
{
// Determine the sign of num and store the result back in caller
if (num < 0.0)
*signp = -;
else
*signp = +;
} // end of separate
We note first, that the asterisk following the type specifiers char, int, and double in the
function header tells the compiler that addresses (pointers) are being passed to the memory
cells signp, wholep, and fractp in separate. The use of the dereference operator in
the executable code causes the appropriate pointer (the pointer to x in this case) to be
dereferenced and used as the address of the cell in which the sign character is to be stored.
It is as though we traversed the pointer from signp to x, and stored the sign character in x
rather than in signp.
You
should now be able to complete the function separate by finding simple
formulas
For
obtaining the whole and fractional parts of num and storing the
result back in y
and
z respectively
(using the dereference operator, as in *wholep
and
*fractp.
In the
next Lecture Set (Set 7B) we use the swap function to reinforce many of the
points
made
here.
