Functions with Addresses as Parameters
Introduction/Motivation
The lab on program management and functions asked you to write two functions:
-
Function circum took a circle's radius as parameter and returned the circumference of the circle.
-
Function area took the circle's radius as parameter and returned the area of the circle.
In both cases, the function specified one parameter (e.g., double radius), performed a computation, and returned the desired result (e.g., return 3.1415926535*radius*radius). Functions, however, are limited in that they can only return one value.
Rather the writing two functions, consider the task of writing one procedure compute_circle that computes both circumference and area. In particular, the challenge is to retrieve two values from the procedure, rather than just one. The basic difficulty is that functions encountered so far have fundamental limitations.
-
A return statement is inadequate for designating multiple values, as only one result is returned. We might be able to use the new procedure compute_circle in an assignment statement
double circumference = compute_circle(radius);
or
double area = compute_circle(radius);
However, in either case, only one value will be stored (e.g, circumference or area); the other value is left out.
-
Parameters might be used, such as
void compute_circle (double radius, double circle_circum, double circle_area)
but the call to compute_circle creates new storage locations for the parameters, circle_circum and circle_area, and values stored there would not be carried back to the main program when the compute_circle procedure finished.
To facilitate general problem solving, C supplies a separate mechanism that allows procedure parameters to refer to data stored elsewhere. This reading explores this approach in some detail.
Reading Outline
Clarifying the Problem
To clarify the relationship between actual parameters (e.g., in the main program) and formal parameters, consider the following program compute-circle-1.c that attempts to compute the circumference and area of a circle.
/* Program to compute circumference and area of a circle, given its radius */ #include <stdio.h> #define pi 3.1415926535 /* procedure uses parameter passage by value THIS DOES NOT WORK! */ void compute_circle (double circle_radius, double circle_circum, double circle_area) { circle_circum = 2.0 * pi * circle_radius; circle_area = pi * circle_radius * circle_radius; } int main () { printf ("program to compute circumference and area of a circle\n"); double radius, circumference, area; /* specify radius */ radius = 3.0; printf (" a circle with radius %lf\n", radius); /* use a procedure to compute circumference and area */ compute_circle (radius, circumference, area); /* report results */ printf (" has circumference %lf and area %lf\n", circumference, area); return 0; }
Memory allocation for program compute-circle-1.c

As discussed in the reading on data storage on the run-time stack, the program first allocates space for the main variables, radius, circumference, and area, on the run-time stack. Procedure compute_circle is called with 3.0 as the value for parameter circle_radius, and space is allocated for the three parameters, circle_radius, circle_circum and circle_area. Further, the appropriate values for circumference and area are computed and stored in the procedure's storage for local variables, circle_circum and circle_area.
The difficulty with program compute-circle-1.c is that procedure compute_circle does not know where in main memory to store the desired results. Local memory within compute_circle is used. What is needed is location information about where to store the data in the main program.
Jargon: The location of data for a variable is called its address, so the "address of a variable or parameter" is the location in main memory where its value is stored.
Passing Addresses
C resolves this problem by providing mechanisms for discovering the addresses or locations of variables and parameters and for accessing data at a specified address. The three basic syntactic elements within C are as follows:
-
During program execution:
- The address operator &: If x is a variable, then &x indicates the "address of" x.
- The access-data operator *: If add is the address of a variable, then *add accesses the data at that address.
-
During procedure or function declaration:
-
* within a parameter or variable declaration: The use of the
* symbol within a parameter or variable declaration indicates the element
will be the address of a value, rather than the value itself. For example,
- The declaration double * ad1 indicates that variable ad1 will be the address of a double
- The declaration int * ad2 indicates that variable ad2 will be the address of a int
-
* within a parameter or variable declaration: The use of the
* symbol within a parameter or variable declaration indicates the element
will be the address of a value, rather than the value itself. For example,
A First Example
These elements are illustrated in program compute-circle-2.c the resolves the difficulties encountered in compute-circle-2.c. The program also includes printing that explicitly indicates the addresses of the various parameters and variables when the program is run.
/* Program to compute circumference and area of a circle, given its radius A CORRECT VERSION! */ #include <stdio.h> #define pi 3.1415926535 void compute_circle (double circle_radius, double * circle_circum, double * circle_area) { printf ("parameter addresses: radius: %10u, circum: %10u, area: %10u\n", (unsigned int) &circle_radius, (unsigned int) &circle_circum, (unsigned int) &circle_area); *circle_circum = 2.0 * pi * circle_radius; *circle_area = pi * circle_radius * circle_radius; printf ("elements stored: radius: %10lf, circum: %10u, area: %10u\n", circle_radius, (unsigned int) circle_circum, (unsigned int) circle_area); } int main () { printf ("program to compute circumference and area of a circle\n"); double radius, circumference, area; printf ("main addresses: radius: %10u, circum: %10u, area: %10u\n", (unsigned int) &radius, (unsigned int) &circumference, (unsigned int) &area); /* specify radius */ radius = 3.0; printf (" a circle with radius %lf\n", radius); /* use a procedure to compute circumference and area */ compute_circle (radius, &circumference, &area); /* report results */ printf (" has circumference %lf and area %lf\n", circumference, area); return 0; }
Memory allocation for one run of compute-circle-2.c:

Each time a program runs, the operating system allocates space for the program and its data. Since different space may be allocated each time, the addresses of the parameters and variables will be different. The following shows the output from one run of this program.
program to compute circumference and area of a circle main addresses: radius: 1483434640, circum: 1483434632, area: 1483434624 a circle with radius 3.000000 parameter addresses: radius: 1483434684, circum: 1483434576, area: 1483434568 elements stored: radius: 3.000000, circum: 1483434632, area: 1483434624 has circumference 18.849556 and area 28.274334
Program Analyzed
Since both the syntax and the execution of this program are different from what we have seen previously, we examine the program in pieces, as the program runs.
As with all C programs, program execution begins with the main.
printf ("program to compute circumference and area of a circle\n"); double radius, circumference, area; printf ("main addresses: radius: %10u, circum: %10u, area: %10u\n", (unsigned int) &radius, (unsigned int) &circumference, (unsigned int) &area);
After printing an opening line, three variables are declared.
-
During execution, space for these variables is allocated on the run-time stack.
-
The second printf statement shows the locations allocated.
- Since &radius is an address, printing can be a little tricky. Here, to allow relatively straight forward interpretation of an address, the code converts the address type to an unsigned int for printing.
- The addresses printed are show in the diagram of the run-time stack (above).
- Since the operating system allocates space anew each time the program runs, these values will likely vary with each program run.
Program execution continues with the initialization of radius and the call to compute_circle
/* specify radius */ radius = 3.0; printf (" a circle with radius %lf\n", radius); /* use a procedure to compute circumference and area */ compute_circle (radius, &circumference, &area);
radius is initialized and its value printed.
In calling compute_circle, the program sends the address of the variables, circumference and area, to the procedure.
-
The ampersand & is C's symbol for the "address of" operator.
-
By sending this information to compute_circle, the procedure will know where to store data values after they are computed.
The call to compute_circle allocates additional space.
void compute_circle (double circle_radius, double * circle_circum, double * circle_area) { printf ("parameter addresses: radius: %10u, circum: %10u, area: %10u\n", (unsigned int) &circle_radius, (unsigned int) &circle_circum, (unsigned int) &circle_area);
Space is allocated for three parameters of compute_circle, and the print statement reports the locations of these new storage locations.
-
circle_radius is a double. In the call, this is initialized by the value of radius (3.0) in the main program.
-
circle_circum is declared as a double *, the address of a double.
- In the call, this variable is initialized to &circumference — the address or location of the circumference variable.
- The value stored for circle_circum is the address of circumference
-
circle_area is declared as a double *, also the address of a double.
- From the call, the value stored is the address of area.
Since the locations of variables in the main program are stored by the compute_circle parameters, the procedure has the information it needs to change the values stored in those variables.
*circle_circum = 2.0 * pi * circle_radius; *circle_area = pi * circle_radius * circle_radius;
Since circle_circum holds the address of circumference in the main program, the program can access that value using the syntax *circle_circum.
-
The circumference of a circle is computed as 2.0*pi*circle_radius.
-
This computed value is stored in the location stored by circle_circum, that is, in the address for circumference in the main program.
A parallel analysis places the computed area of the circle in the variable area in the main program. (The variable circle_area stores the address of area, so that compute_area knows where to store the computed area value.)
Altogether, the mechanism of storing an address allows procedure compute_circle to change specific variables in the main program!
printf ("elements stored: radius: %10lf, circum: %10u, area: %10u\n", circle_radius, (unsigned int) circle_circum, (unsigned int) circle_area);
This print statement reports the values actually stored in the parameters.
-
The parameter circle_radius contains the value 3.0, the value of the radius from the main program that serves as the basis for the computation of both circumference and area.
-
The parameter circle_circum stores the address of circumference in the main program. If one looks at that address on the run time stack, one finds the actual computed circumference.
-
The parameter circle_area stores the address of area in the main program. Going to that location on the run-time stack yields the computed area.
When procedure compute_circle is finished, program execution returns to the main program.
/* report results */ printf (" has circumference %lf and area %lf\n", circumference, area); return 0;
Since work within compute_circle has placed computed values into the variables declared in the main program, the program itself can finish by printing the values already stored.
A Second Example
Once again, consider the problem to simulate the expected number of children for a couple who decide to have children until they have at least one boy and at least one girl. In the reading on program management and functions, we first printed the results for 20 couples, giving the number of boys, girls, and total number of children for each couple. The next versions of the simulation program focused on the results for 1000 couples and printed the average number of children and the maximum family size in the simulation. These simulations lost information about the numbers of girls and the numbers of boys, but rather recorded the total number of children. The most recent version, program couple-6.c organized work into three main pieces:
-
main controlled the overall simulation.
-
simulate_several_couples organized and tabulated results for many couples, given the likelihood that a child was a boy.
-
simulate_couple handled details of the simulation for an individual couple.
Since simulate_couple was a function that could return only one value, the program was organized so that simulate_several_couples processed the running sum of children and the maximum number of children. However, because simulate_couple could return only one value, this organization did not allow the overall program to keep track of separate numbers or maxima for girls or for boys.
Program couple-7.c takes advantage of parameters with addresses to provide more complete record keeping by gender.
In couple-y.c,
-
main handles overall processing coordinate, just as in couple-6.c.
-
simulate_several_couples maintains variables for the number of girls, the maximum number of girls, the number of boys, and the maximum number of boys. Since processing for an individual couple will affect these counts, the addresses of the relevant variables are passed into the simulation procedure for an individual couple.
-
simulate_couple handles details for an individual couple. As part of this work, an initial simulation is run for a couple. Then, the on-going counts for the number of girls and boys and the maximum girls and boys are updated, using the address parameters passed into the procedure.
The following discussion examines each procedure separately for couple-7.c. The complete program couple-7.c puts these procedures together. The diagram at the right shows the run-time stack for this program, shortly after simulate_couple is called for the first time, The output for one run of this program follows.

Output from one run of couple-7.c.
Simulation of family size with 1000 couples fraction avg. max. avg max. avg. boys girls girls boys boys children 0.350 2.1 13 1.2 5 3.3 0.400 2.0 14 1.2 6 3.2 0.450 1.8 14 1.3 8 3.1 0.500 1.5 15 1.4 10 3.0 0.550 1.4 8 1.7 8 3.1 0.600 1.3 8 1.8 14 3.1 0.650 1.2 6 2.1 13 3.4
main procedure
int main () { /* initialize pseudo-random number generator */ /* change the seed to the pseudo-random number generator, based on the time of day */ srand (time ((time_t *) 0) ); /* print initial title for program */ printf ("Simulation of family size with %d couples\n", numberOfCouples); /* print table heading */ printf ("fraction avg. max. avg max. avg. \n"); printf (" boys girls girls boys boys children\n"); /* run simulation for several couples */ double boy_fraction; for (boy_fraction = 0.35; boy_fraction <= 0.65; boy_fraction += 0.05) { simulate_several_couples (numberOfCouples, boy_fraction); } return 0; }
- The program begins initializing the random number generator and printing headings for a table of averages and maximum family sizes.
-
The start of this program includes
#include numberOfCouples 1000
so the compiler inserts 1000 for numberOfCouples in the procedure call to simulate_several_couples.
-
The main loop allows an exploration of family sizes, based on the fraction of boys expected. Typical results show
- the number of girls in a family tends to be larger when the fraction of boys is relatively small.
- the average family size increases moderately when the fraction of boys moves away from 0.50 (50%).
Overall, the main procedure in couple-7.c is very similar to the corresponding procedure in couple-6.c
simulate_many_couples Procedure
/* procedure to conduct simulation for several couples parameter numCouples: the number of couples to be simulated parameter fraction_boys: the percentage of boys born, expressed as a decimal fraction */ void simulate_several_couples (int numCouples, double fraction_boys) { int couple; int total_girls = 0; int total_boys = 0; int max_girls, max_boys; for (couple = 1; couple < numCouples; couple++) { simulate_couple (fraction_boys, &total_girls, &max_girls, &total_boys, &max_boys); } double avg_children = ((double) (total_girls+total_boys)) / numCouples; printf (" %6.3lf %6.1lf %6d %6.1lf %6d %6.1lf\n", fraction_boys, (double) total_girls/numCouples, max_girls, (double) total_boys /numCouples, max_boys, avg_children ); }
-
The primary variables for family size (e.g., number and maximum of girls and of boys) are declared here, as these counts will need to be updated with the simulation of each couple.
-
In the call to simulate_couple,
- The value fraction_boys is passed as an actual parameter. Since this value provides information to simulate_couple, but the value will not be changed for an individual couple, the call provides the value only (no address).
- In simulating one couple, the total number of girls and boys will change, and the maximum number of boys or girls might change. To allow changes in these variables, the call passes the address of the variables — not their values.
-
After the simulations for 1000 couples, the statistics are computed and printed.
Note that although this procedure coordinates the simulation for a given fraction of boys, the actual code is reasonably short and concise. Many details of the simulation for a couple are handled by the simulate_couples procedure.
simulate_couple Procedure
/* procedure to simulate the number of children for one couple parameter fraction_boys: the percentage of boys born, expressed as a decimal fraction pre-condition: 0.33 <= fraction_boys <= 0.66 */ void simulate_couple (double fraction_boys, int * num_girls, int * max_girls, int * num_boys, int * max_boys ) { /* actively enforce the pre-condition */ assert ((0.33 <= fraction_boys) && (fraction_boys <= 0.66));
-
The fraction_boys parameter provides the value for determining the likelihood of a child being a boy. This value is copied onto the run-time stack directly.
-
Parameters to count the number or maximum of girls or boys all have type int *. That is, each parameter indicates the address of a corresponding variable in the simulate_many_couples procedure. For these parameters, the run-time stack stores an address; to find the corresponding value, one must look at the run-time stack at that address.
/* couple starts with no children */ int boys = 0; int girls = 0; /* couple has children */ while ((boys == 0) || (girls == 0)) { if ((((double) rand()) / ((double) RAND_MAX)) < fraction_boys) boys++; else girls++; }
-
The simulation initialization and loop for successive children of a couple is identical to previous versions of this simulation — no changes here!
/* update records for girls and boys */ if (* num_girls == 0) { // this is first couple, so set all variables explicitly *num_girls = * max_girls = girls; *num_boys = * max_boys = boys; } else { // update variables, as appropriate *num_girls += girls; if (girls > *max_girls) *max_girls = girls; *num_boys += boys; if (boys > *max_boys) *max_boys = boys; } }
-
Recording of data depends upon whether this is the first couple or subsequent couples.
- For the first couple, the number of girls and boys is also the maximum.
- For subsequent couples, the number of girls and the number of boys should be added to past totals. Also, maximum values must be checked and updated, as needed.
-
Since num_girls is the address of a variable (in simulate_many_couples), the program must fetch the value from that address to determine how many girls have been recorded previously. In C, when num_girls is an address, * num_girls (with the * added) retrieves the value stored at that address.
- * num_girls==0 tests if any past couples have had girls. (Since each couple will have at least one girl and at least one boy, zero girls in the past must imply this is the first couple.)
- *num_girls += girls retrieves the previous number of girls (from the address stored in num_girls), adds girls for the current couple, and stores the result back in the address stored in num_girls.
- *max_girls=girls stores the number of girls in the address stored in max_girls (i.e., the location for the variable max_girls in the simulate_many_girls procedure).
created 13 August 2016 by Henry M. Walker revised 13-16 August 2016 by Henry M. Walker |
![]() ![]() |
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |