Functions
Introduction
As with Scheme, a function or procedure in C is designed to perform a task. Several variations are possible, such as the following:
- The task may be self contained, or it it may depend upon some starting data.
- The task may or may not change its environment (e.g., the task might move the robot or print something to a terminal window).
- When the task finishes, data may or may not be computed and returned for use in the main program.
Altogether, a function or procedure performs some work. They are used to abstract the work into a single coherent operation. Variations arise in whether or not data are needed to start the work and whether or not computed results are returned to the main program.
In this reading, we examine how functions are created and used in the
C programming language. Some functions may take values as parameters,
as you are likely accustomed to in Scheme. Similarly, some functions
return values, while others are simply called for their side
effect. When such functions produce no value, their return type is
void.
Textbook Reading
Begin with a reading from your textbook:
- King: Sections 9.1-9.2, 9.4, pages 183-192, 201-202
Examples
Using the textbook as background, examine the following programs.
quadratic
/* program to illustrate 4 functions related to the quadratic formula
given a, b, and c, the formula computes solutions to
a * x^2 +b * x + c = 0
if discriminant = sqrt (b*b - 4*a*c), a != 0, and discriminant > 0,
then the two real solutions of this equation are
(-b + discriminant) / (2.0 * a)
and
(-b - discriminant) / (2.0 * a)
Note: When compiling, link with the math library using the "-lm" flag. E.g.,
cc -lm -o quadratic quadratic.c
*/
#include
#include
/* Print a quadratic equation given the coefficients. */
void
printEquation (double coeff2, double coeff1, double coeff0)
{
printf ("Equation: %lf*x^2 + %lf*x + %lf = 0\n", coeff2, coeff1, coeff0);
} // printEquation
/* Compute the discriminant of a quadratic equation
* r*x^2 + s*x + t = 0 given the coefficients r, s, and t.
*
* Precondition:
* s*s >= 4*r*t
* Postcondition:
* returns the square root of s*s - 4*r*t
*/
double
computeDiscriminant (double r, double s, double t)
{
return sqrt (s*s - 4*r*t);
} // computeDiscriminant
/* Print both roots of a quadratic equation a*x^2 + b*x + c = 0.
*
* Preconditions:
* Both roots must be real
* a != 0
*/
void
printRoots (double a, double b, double c)
{
double discriminant = computeDiscriminant (a, b, c);
double root1 = (-b + discriminant) / (2.0 * a);
double root2 = (-b - discriminant) / (2.0 * a);
printf (" Roots: %lf and %lf\n", root1, root2);
} // printRoots
/* Control processing for x^2 - 3*x + 2.0 = 0: prints equation and its roots */
void
displayExample1 (void)
{
printEquation (1.0, -3.0, 2.0);
printRoots (1.0, -3.0, 2.0);
} // displayExample1
/* Control processing for 2.0*x^2 - 7.0*x - 4.0 = 0;
* prints equation and its roots */
void
displayExample2 (void)
{
printEquation (2.0, -7.0, -4.0);
printRoots (2.0, -7.0, -4.0);
} // displayExample2
/* Main function illustrating two equations with their solution via the
* quadratic formula */
int
main (void)
{
printf ("program illustrating functions and the quadratic formula\n");
displayExample1();
displayExample2();
return 0;
} // main
yoyo
/* program to use functions with value parameters to control a robot */
#include
#include
#include
/* yoyo illustrates:
function with 1 parameter: count
2 local variables: i, reps
1 return value
*/
int
yoyo (int count)
{
int reps = 3*count;
/* repeat motion */
for (int i = 0; i < reps; i++)
{
rForward (1, .5);
rBackward (1, .5);
}
sleep (3);
/* print local variables */
printf ("in yoyo: count = %d, reps = %d\n", count, reps);
return reps;
} // yoyo
/* main demonstrates a call to function with return value */
int
main (void)
{
rConnect ("/dev/rfcomm0");
int repetitions, result;
repetitions = 2;
result = yoyo (repetitions);
printf ("repetitions = %d, result = %d\n", repetitions, result);
rDisconnect();
return 0;
} // main
value-params
/* value-params.c
*
* Demonstrates scoping of variable names and passing values into functions.
*/
#include
int
procA (int a, int b)
{
printf ("procA1 a=%d, b=%d\n", a, b);
a = 5;
b = 6;
printf ("procA2 a=%d, b=%d\n", a, b);
return a + b;
} // procA
void
procB (int x, int y)
{
int z = procA (x, y);
printf ("procB1 x=%d, y=%d, z=%d\n", x, y, z);
x = 5;
y = 6;
printf ("procB2 x=%d, y=%d, z=%d\n", x, y, z);
} // procB
int
main (void)
{
int x, y;
x = 2;
y = 3;
printf ("main1 x=%d, y=%d\n", x, y);
procB (y, x);
printf ("main2 x=%d, y=%d\n", x, y);
return 0;
} // procB
Schematic Memory Diagrams
When analyzing a C program, it often is helpful to create a schematic of a computer's memory. Overall, each procedure requires memory for its parameters and any local variables. This amount of space is allocated whenever the procedure is called, and this space is deallocated when the procedure finishes.
In a previous reading, we called abstract versions of these notions stack diagrams. We briefly revisit this notion now that we have a firmer idea of functions that take parameters as well.
The stack diagrams we draw are augmented with function parameters appearing in the stack frame alongside the local variables for the function. When the function returns and the stack frame is removed, all the local variables and parameters for that function call are removed.
To clarify, consider the
original yoyo.c program.
When the program begins, space is allocated for the
variables, repetitions and result in
the main procedure. The
assignment repetitions = 2; stores the number
2 in the location for the repetitions variable.
When the yoyo procedure is called, new space is
allocated for the parameter count and for local
variables i and reps. During the call
to yoyo, the value of the variable repetitions is
copied into the space for the parameter count. That is, the value
2 is now stored in two places (e.g., repetitions
and count).
When yoyo executes, reps is given the value
6. The variable i takes on successive values 0, 1, 2,
3, 4, 5, and 6.
As yoyo finishes, it returns the value of reps (6).
When yoyo is finished, its space is deallocated, and the
returned value is stored in variable result.
Self-check exercise
Consider the following C function:
void
change (int x)
{
x = 3;
} // change
And the following main function that exercises change:
int
main (void)
{
int x = 0;
change (x);
printf ("%d\n", x);
}// main
-
Before running the program, predict what
mainprints to the console. - Verify your prediction by compiling and running the program.
-
Explain the results using
the stack-based
model of computation. Function parameters are simply local
variables (initialized to their arguments). What value does
xinchangecontain and what is the effect of the assignment inchange?
