As with Scheme, a function or procedure in C is designed to perform a task. Several variations are possible, such as the following:

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:


Using the textbook as background, examine the following programs.


/* 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)
     (-b - discriminant) / (2.0 * a)
  This variant was created by Jerod Weinman, based on a program developed by 
  Henry Walker.  The majority of the differences are reflected in the 
  comments explaining the quadratic equation.

  compile, linking with the math library, using the line
      cc -lm -o quadratic quadratic.c

#include <stdio.h>
#include <math.h>

/* Print a quadratic equation given the coefficients. */
printEquation (double coeff2, double coeff1, double coeff0)
  printf ("Equation:  %lf*x^2 + %lf*x + %lf = 0\n", coeff2, coeff1, coeff0);
} // printEqn

/* 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
computeDiscriminant (double r, double s, double t)
  return sqrt (s*s - 4*r*t);
} // disc

/* Print both roots of a quadratic equation a*x^2 + b*x + c = 0.
 * Preconditions:
 *   Both roots must be real
 *   a != 0
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 */
displayExample1 (void)
  printEquation (1.0, -3.0, 2.0);
  printRoots (1.0, -3.0, 2.0);
} // eqn1

/* Control processing for 2.0*x^2 - 7.0*x - 4.0 = 0; 
 * prints equation and its roots */
displayExample2 (void)
  printEquation (2.0, -7.0, -4.0);
  printRoots (2.0, -7.0, -4.0);
} // eqn2

/* Main function illustrating two equations with their solution via the 
 * quadratic formula */
main (void)
  printf ("program illustrating functions and the quadratic formula\n");


  return 0;
} // main


/* program to use functions with value parameters to control a robot */

#include <stdio.h>
#include <MyroC.h>
#include <unistd.h> 

/* yoyo illustrates:
      function with 1 parameter:  count
                    2 local variables: i, reps
                    1 return value
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 */
main (void)
  rConnect ("/dev/rfcomm0");

  int repetitions, result;
  repetitions = 2;
  result = yoyo (repetitions);
  printf ("repetitions = %d,   result = %d\n", repetitions, result);

  return 0;
} // main


/* value-params.c
 * Demonstrates scoping of variable names and passing values into functions.

#include <stdio.h>

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

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

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.

main stack frame

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.

yoyo and main stack frames

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

main stack frame with result

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:

change (int x)
    x = 3;
} // change

And the following main function that exercises change:

main (void)
    int x = 0;
    printf("%d\n", x);
}// main
  1. Before running the program, predict what main prints to the console.
  2. Verify you prediction by compiling and running the program.
  3. Explain the results using the stack-based model of computation. Function parameters are simply local variables (initialized to their arguments). What value does x in change contain and what is the effect of the assignment in change?
The Self-check exercise a derivative work of an exercise by Titus Klinge and Peter-Michael Osera, used under a Creative Commons Attribution-NonCommercial 4.0 license.