CSC 115.005/006 Sonoma State University Spring 2022
Scribbler 2
CSC 115.005/006:
Programming I
Scribbler 2
Instructor: Henry M. Walker

Lecturer, Sonoma State University
Professor Emeritus of Computer Science and Mathematics, Grinnell College


Course Home References Course Details: Syllabus, Schedule, Deadlines, Topic organization MyroC Documentation Project Scope/
Acknowledgments

Notes:

Functions with Addresses as Parameters

Introduction/Motivation

The lab on program management and functions asked you to write two functions:

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.

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

allocation of memory for 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:


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:

allocation of memory for 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.


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 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.


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.

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.


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:

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,


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.

allocation of memory for couple-7.c

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;
}

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
          );
}

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));

  /* 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++;
    }


  /* 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;
    
    }
}


created 13 August 2016 by Henry M. Walker
revised 13-16 August 2016 by Henry M. Walker
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.