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:
-
The area of the entire circle is π r2. In this case, the radius is 2, so the area of the entire circle is 4π. Thus, the area of the part of the circle in the first quadrant is π.
-
The area of the square in the first quadrant is 2*2=4.
-
The probability of a point in the square also being in the circle is
(area of circle) / (area of square) = π / 4.
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 π.

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:
-
gen_and_check_point picks a random point in the square and then determines if the point is also in the circle.
-
conduct_trials tabulates the results when picking a large number of points.
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.
-
One area is allocated for global variables (i.e., MaxRandDouble and NumberOfTrials in this program. As part of this work, the global variables are initialized.
-
A block of space is allocated for functions and procedures. As the program runs, some of this space will be assigned to a function when it is called, and the space will be de-assigned when the function has finished its work. This allocated space is called the run-time stack.
-
Once space for the run-time stack is allocated, one end of that space is allocated for the main procedure — the first procedure called when a C program begins.
In allocating space for main, the compiler has determined what variables will be needed within main (in this case, number_in and pi_approx). Although space is allocated, the variables may not be initialized. Rather, the values stored in this allocated space is likely whatever was stored there from the run of a previous program.
-

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:
-
Space is assigned on the run-time stack for conduct_trials.
- Since conduct_trials utilizes two formal parameters (numTrials and maxRandReal) and two new variables (i and counter) space for these variables must be included within the newly assigned space on the run-time stack.
- When conduct_trials is done, the function must know that it should return to the proper place within main, and that information also is recorded. (This return address and other possible administrative details are not shown in the diagram.)
-
Once space is assigned for conduct_trials, the actual parameters (NumberOfTrials and MaxRandDouble) are evaluated, and their values are copied into the locations assigned for the formal parameters (numTrials and maxRandReal).
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:
-
The values of NumberOfTrials and MaxRandDouble are copied into locations for numTrials and maxRandReal. Thus, there are two copies of these data elements. As we shall see later in this reading, one consequence of this call of conduct_trials is that a change in a formal parameter (e.g., if numTrials was changed to 15000), this change impacts processing in conduct_trials, but the original value remains in the actual variable NumberOfTrials that is stored in main.
-
With each call to a function, separate space is allocated for its formal parameters and declared variables. Parameters and variables declared in one call of a function are entirely separate from those in other functions. Even if the variable names in one function are the same as those in another function, separate space has been allocated for each function, so the use of variables in one function is completely independent of the values and use of variables in another function. With this isolation of variables within a function, the newly declared variables are called local variables.
-
In contrast to local variables that are declared within a function, the global variables, declared outside main or any other function in the program, are collected in their own block of memory. Those global variables can be accessed from any function or procedure.

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.
- Since gen_and_check_point utilizes one formal parameter (max_rand) and two new variables (x and y) space for these variables must be included within the newly assigned space on the run-time stack.
- When gen_and_check_point is done, the function must know that it should return to the proper place within conduct_trials, and that information also is recorded. (This return address and other possible administrative details are not shown in the diagram.)
-
Once space is assigned for gen_and_check_point, the actual parameter (maxRandReal) is evaluated, and its value is copied into the location assigned for the formal parameter (max_rand).
In considering this process of memory allocation within the run-time stack, it is important to emphasize two points:
-
The names of the formal parameters may be the same or different from the names of the actual parameters. In either case, new space is assigned for the formal parameter and a value copied from the actual parameter, resulting in two copies of the value. Once the copying takes place, the two values are completely independent, and a change in one has no bearing on the other.
-
Space for the local variables within a function is assigned when the function is called, and the local variables for one function are completely independent from those in another function. Local variables in several functions may or may not have the same names, but the storage and values stored are separate in any case.

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,
-
0.5317 might be stored in x
-
1.732 might be stored in y
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.
-
All space for gen_and_check_point is de-assigned; all information about that function is no longer available.
-
The value returned by gen_and_check_point has been used in processing within conduct_trials, and the counter variable is updated appropriately.

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,
-
the value of the actual parameter maxRandReal from conduct_trials is copied into the storage assigned for max_rand in gen_and_check_point.
-
since space has just be assigned for gen_and_check_parameter, we should not make assumptions about what values are stored for x and y, so the diagram shows these locations as blanks.

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,
-
All 1000 trials will have been conducted, picking points at random in the square and checking if they are also in the circle.
-
Since index variable i has counted 10000 completed trials, its value is now 10001 — reflecting that no further trials are needed.
-
The counter variable records the number of points in the circle (e.g., 7854 in this example).

Data Storage: main Finishes
When conduct_trials has finished processing,
-
the number of points in the circle is returned to main and stored in the variable points_in. (7854 in the current example.)
-
space for conduct_trials is de-assigned.
-
Processing in main resumes
- pi_approx is computed (3.1416 in this example).
- the approximation to Pi is printed
When main is completed, space for its variables and for the program's global variables is deallocated, and the program terminates.

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:
-
If a parameter or variable is declared in a function, then all references in that function related to the locally declared parameters and variables.
-
If a variable name is not declared in a specific function, then the program looks for a global variable by that name.
-
If a variable name is not declared either in a function or globally, then any reference to that name within the function is considered undeclared, and a compiler error will result.
Jargon: The scope of a variable is the region of the program where the variable can be defined and used. In our examples,
-
parameters and local variables have scope within the function where they are defined.
-
Global variables have scope throughout the program.
However, the following example shows that the names of global variables can be redefined within a procedure. When a function redefines the name of a global variable, the global variable is still there, but we may not be able to access it directly. (We'll discuss this point further in the next several class sessions.)
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.
/* 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

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

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

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

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

created 7 August 2016 by Henry M. Walker revised 9-12 August 2016 by Henry M. Walker |
![]() ![]() |
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |