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:

The Run-time Stack

Introduction

Discussion in this reading utilizes the following problem as motivation. (The problem also illustrates a common type of simulation, called a Monte Carlo Method, in which approximate solutions are found by using random numbers.)

Problem: Consider a circle of radius 2, centered at the origin, and restrict your attention to the part of the circle in the first quadrant. Also, consider a square of size 2 in the first quadrant, with one corner at the origin. With this geometrical configuration, the circle is completely within the square, as shown in the figure.

Question: If one picks a point at random within the square, what is the probability that the point will also be within the circle?

Analytical Solution: We can compute the probability by comparing areas:

Thus, 4 times the probability of the point being in the circle is π

Simulated Solution: To approximate the solution, we could write a program that randomly picks points in the square and counts the number that also are in the circle. The fraction of points also in the circle should be about π/4, and 4 times that fraction should give an approximation to π.

circle of radius 2 in the first quadrant

A Simulation Program to Compute π

The program pi-sim.c provides a simulation for this problem, while illustrating many elements of program organization and C previously discussed in this course.


/*  This program approximates Pi by picking a points in a square and   *
 *  and determining how often they are also in an appropriate circle.  */

#include <stdio.h>

/* libraries for the pseudo-random number generator */
#include <stdlib.h>
#include <time.h>

/* function prototypes */

/* generate a point in the square and determine if it is in the circle
 * parameter max_rand:  the maximum size of a random number, as a double
 * return:  1 if the generated point is in the circle
            0 if not
*/
int gen_and_check_point (double max_rand);

/* perform simulation for many trials
 * parameter numTrials:  number of points to be considered
 * return:  number of generated points in circle
 */
int conduct_trials (int numTrials, double maxRandReal);

The program utilizes two functions:

This program is organized by identifying these programs near the beginning of the file, using function prototypes. The details of each function are given near the end of the file.


/* global variables */
int MaxRandDouble = (double) RAND_MAX;
int NumberOfTrials = 10000;

Rather than use #define to declare simulation constants, we declare variables outside main or other functions. These variables are called global variables. As we shall see shortly, global variables may be used by main or any other function in the program.

Global variables may be declared and initialized. However, additional processing of these variables outside of a function is not allowed in C. For example, we could not call scanf to read values for these variables outside of main or other function. A call to scanf could be placed within main or other function, but here.


int main ()
{
   /* initialize pseudo-random number generator */
   printf ("This program approximates Pi by picking %d points in a square\n", 
            NumberOfTrials);
   printf ("and counting the number in an appropriate circle.\n\n");

   // initialize random number generator, based on the time of day
   srand (time ((time_t *) 0) );

   /* conduct simulation */
   int number_in = conduct_trials (NumberOfTrials, MaxRandDouble);
   double pi_approx = 4.0 * number_in / NumberOfTrials ;

   /* report results */
   printf ("The approximate value of Pi is %.5lf .\n", pi_approx);
   return 0;
}

After printing an opening message, the random number generator is initialized.

The simulation itself involves calling conduct_trials to obtain the number of points inside the circle.

For the simulation itself, this program divides the computation into a few steps, using variables frac_in and pi_approx. Although the program could be simplified by combining these steps (perhaps placing the entire computation within the printf statement), the use of these variables will be helpful in the discussion that follows..


/* full function bodies */
/* generate a point in the square and determine if it is in the circle
 * parameter maxReal:  the maximum size of a random number, as a double
 * return:  1 if the generated point is in the circle
            0 if not
*/
int gen_and_check_point (double max_rand)
{
  double x, y;
  x = 2.0 * rand() / max_rand;
  y = 2.0 * rand() / max_rand;
  return (x*x + y*y <= 2.0*2.0);
}

Since rand gives values between 0 and max_rand, rand()/max_rand would give values between 0.0 and 1.0. Multiplying by 2.0 gives values between 0.0 and 2.0 — appropriate x and y values for coordinates of a square of side 2.

The test (x*x + y*y <= 2.0*2.0) will be true (e.g., 1 in C) if the point lies inside the circle and false (e.g., 0 in C) otherwise.


/* perform simulation for many trials
 * parameter numTrials:  number of points to be considered
 * return:  number of generated points in circle
 */
int conduct_trials (int numTrials, double maxRandReal)
{
  int i;
  int counter = 0;
   // pick points in first quadrant with coordinates between 0 and 1
   // determine how many are in the circle of radius 1
   for (i = 1; i <= NumberOfTrials; i++) 
     {
       if (gen_and_check_point (maxRandReal)) counter++;
     }

   return counter;
}

Conducting trials requires generating points and counting the number that are found in the circle.


Data Storage Part 1: Global Variables and the Main procedure

In order to start a discussion of memory allocation when a program runs, we consider in some detail when and where data are stored for the pi-sim.c program.


When a program is loaded into main memory for execution, the operating system allocates at least two blocks of space for variables.

global data and main variables

Data Storage Part 2: conduct_trials called

As main begins to execute, some printing takes place and the random number generator is initialized.

Next, function conduct_trials is called with two parameters. This function call involves several steps:

With this work done, the computer starts its execution of the conduct_trials function.

This set up in calling conduct_trials has several important consequences:

global data, main, and conduct_trials variables

Data Storage Part 3: gen_and_check_point called

As conduct_trials begins to execute, the counter variable is set to 0. Then, within the for loop, the index variable i is set to 1.

At this point, the function gen_and_check_point is called with formal parameter max_rand, so again space must be assigned for this new function.

In considering this process of memory allocation within the run-time stack, it is important to emphasize two points:

global data, main, conduct_trials, and gen_and_check_point variables

Data Storage Part 4: gen_and_check_point returns

When gen_and_check_point runs, random numbers for x and y are computed and placed in their assigned storage locations. For example,

Depending upon the values computed for x and y, the conditional expression will be true or false. With the example values given, x*x + y*y is approximately 3.28253, the condition is true, and the value 1 is returned.

When the work of gen_and_check_point is completed, its space on the run-time stack is de-assigned, and its return value (e.g., 1 in the example) is returned. Thus, the if statement

if (gen_and_check_point (maxRandReal) counter++;

becomes

if (1) counter++;

Since any non-zero number in C is considered true, the counter variable is incremented (from 0 to 1).

At this stage in processing, the run-time stack is configured as shown at the right. In particular, the run-time stack has these characteristics.

after gen_and_check_point returns first time with point in circle

Data Storage Part 5: gen_and_check_point is called again

As processing in conduct_trials continues, index variable i is incremented (from 0 to 1), and gen_and_check_point is called again.

As with the previous call to gen_and_check_point, space is allocated for parameter max_rand and local variables x and y.

In the figure,

second call to gen_and_check_point

Data Storage Next Parts

As processing in conduct_trials continues, index variable i will be incremented each time through the loop, the function gen_and_check_point will be called, space will be allocated, the value of actual parameter maxRandReal will be copied, the function gen_and_check_point will be run, values will be assigned for x and y, true or false (1 or 0, respectively) will be returned, space for gen_and_check_point will be de-assigned, and counter will incremented as appropriate.


Data Storage: conduct_trials Finishes

Eventually in this processing, the trials will be completed within conduct_trials. In particular,

second call to gen_and_check_point

Data Storage: main Finishes

When conduct_trials has finished processing,

When main is completed, space for its variables and for the program's global variables is deallocated, and the program terminates.

second call to gen_and_check_point

Variable Scope: Interpreting Data When Names are Used in Several Places

The program pi-sim.c was carefully designed so that all parameters and variables had different names. One variable might be used as an actual parameter in a procedure call, but the value had a different name when used as a formal parameter in a separate function. Within that context, we examined carefully how storage was allocated, when variables were assigned storage space, and how parameter values were copied when a function was called.

When variable or parameter names are the same in various functions, the same principles apply, but clarification can be helpful in identifying what parameter or variable is being referenced at each stage in a program. In general, these basic principles apply:

Jargon: The scope of a variable is the region of the program where the variable can be defined and used. In our examples,


The following program value-param-example.c illustrates these principles. For clarity, after listing the program, we trace its execution, examine its use of local and global variables, and discuss storage allocation of global variables and the run-time stack.


/* Program to illustrate resolution of name references
   in programs with functions, value parameters, 
   and local parameters */

#include <stdio.h>

/* global variables */
int a = 1000;
int b = 4000;
int c = 7000;

/* procedure declarations
   note:  main calls proc2 which calls proc1
*/
void proc1 (int b)
{
  int a = c;
  printf ("proc1-1 a:%4d, b:%4d, c:%4d\n", a, b, c);
  b = 600;
  c = 800;
  printf ("proc1-2 a:%4d, b:%4d, c:%4d\n", a, b, c);
}

void proc2 (int b, int c)
{
  printf ("proc2-1 a:%4d, b:%4d, c:%4d\n", a, b, c);
  a = 300;
  b = 500;
  proc1(a);  
  printf ("proc2-2 a:%4d, b:%4d, c:%4d\n", a, b, c);
}

int main ()
{
  int a;
  printf ("main-1  a:%4d, b:%4d, c:%4d\n", a, b, c);
  a = 2000;
  proc2(a, b);
  printf ("main-2  a:%4d, b:%4d, c:%4d\n", a, b, c);

  return 0;
}

This program generated the following output during one test run:

main-1  a:1854033974, b:4000, c:7000
proc2-1 a:1000, b:2000, c:4000
proc1-1 a:7000, b: 300, c:7000
proc1-2 a:7000, b: 600, c: 800
proc2-2 a: 300, b: 500, c:4000
main-2  a:2000, b:4000, c: 800

In case this output seems strange, the commentary below provides a careful explanation.


To understand how this program works, we trace the code and look carefully at the run-time stack as execution progresses.
/* global variables */
int a = 1000;
int b = 4000;
int c = 7000;

int main ()
{
  int a;
  printf ("main-1  a:%4d, b:%4d, c:%4d\n", 
          a, b, c);
  a = 2000;

Space for global variables is assigned

When main starts, space is assigned on the run-time stack for a, but this variable is not initialized. Thus, the value of the local variable a will be whatever happens to be at that location.

In printing, a is a local, uninitialized variable. Both b and c are not declared in main, so the global variables are referenced and printed.


Output so far:

main-1  a:1854033974, b:4000, c:7000
starting main

Program execution continues in main with the call

proc2(a, b);

Work with proc2 continues with

void proc2 (int b, int c)
{
  printf ("proc2-1 a:%4d, b:%4d, c:%4d\n", 
          a, b, c);
  a = 300;
  b = 500;

In calling proc2(a, b) in main.

  • Since a is declared locally, its local value (2000) is used.
  • Since b is not declared locally, the global value (4000) is used.

These values are passed into proc2 as values for the formal parameters b and c. Since b and c are declared within proc2, all references to these names refer to the local parameters.

Since a is not declared separately within proc2, its initial value is found in the global variable.

In reviewing scope, the assignment to a changes the global variable, but the assignment to b changes the parameter copied when proc2. Since the formal parameter b is separate from the call of proc2 in main, changes in proc2 to b reflect the local copy only, not whatever value might have been passed into the procedure.


The next line of output is

proc2-1 a:1000, b:2000, c:4000
starting proc2

Program execution continues in proc2 with the call

proc1(a);

Work with proc1 continues with

void proc1 (int b)
{
  int a = c;
  printf ("proc1-1 a:%4d, b:%4d, c:%4d\n", 
          a, b, c);
  b = 600;
  c = 800;
  printf ("proc1-2 a:%4d, b:%4d, c:%4d\n", 
          a, b, c);
}

In calling proc1 from proc2, the name a refers to the global variable (now with value 300), since there is no variable or parameter a declared in proc2.

The value actual parameter a (300) is copied to formal parameter b) in proc1.

Within proc1,

  • A local variable a is declared and initialized to c. Since the variable c is not declared in proc1, c is global, now having value 7000.

  • After printing, variables b and c are assigned new values.

    b is declared as a parameter, so the value copied to b is changed.

    c is not declared in proc1, so the assignment changes the global variable.


The next two lines of output are

proc1-1 a:7000, b: 300, c:7000
proc1-2 a:7000, b: 600, c: 800
starting proc2

After proc1 is finished, its space is deallocated, and work continues with printf within proc2.

  printf ("proc2-2 a:%4d, b:%4d, c:%4d\n", 
          a, b, c);

As discussed previously for proc2,

  • a is not declared in proc2, so the global variable is used.

  • Both b and c are declared as formal parameters, so the local storage within proc2 is consulted for their values.


The next line of output (printed from proc2 is

proc2-2 a: 300, b: 500, c:4000
starting proc2

Finally, proc2 finishes, its space is de-assigned from the run-time stack, and control returns to the last printf statement in main

 printf ("main-2  a:%4d, b:%4d, c:%4d\n", 
          a, b, c);

Variable a is declared locally, so its local value (2000) is used.

Both b and c are not declared in main, so their global values are used.


The last line of program output (from main) is

main-2  a:2000, b:4000, c: 800
starting proc2


created 7 August 2016 by Henry M. Walker
revised 9-12 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.