Functions

(read chapter 4)

A function is like a small program. It contains:

A function is called (or invoked) by some other part of a program. When a function is called, the statements in the function are executed.

This is useful for:

Two kinds of functions:

built-in functions
defined in standard libraries of functions that come with the C compiler

E.g.: printf, scanf

user-defined functions
written by the programmer
A function may accept some input when it is called, and may return a value to the code that called it. E.g. printf takes a string to print, but doesn't return anything interesting.

Function Calls

The interface of a function (its name, parameters and return type) tell other parts of the program how to call the function. This interface is called the function prototype.

E.g. the prototype for the standard function sqrt:

  double sqrt(double x);

The parameter name in a prototype is just a placeholder that can be omitted:

  double sqrt(double);
has the same meaning.

In either case, the prototype says that function sqrt is called with a type double parameter and returns a value of type double.

In a program, a function call can be used anywhere an expression can be used:

  double result;

  result = sqrt(2.0);
Another example:
  double val = 23.0;

  printf("One more than the square root ");
  printf("of one more than val is %lf\n",
         sqrt(val + 1) + 1);

A function call usually can't occur on the left hand side of an assignment statement. For example:

  sqrt(4.0) = 2.0;
is illegal.

If a function takes multiple parameters, they are separated by commas in the prototype. For example:

  int foo(char c, char c2, double d);
or equivalently:
  int foo(char, char, double);

In ANSI C, type void is used in the prototype when a function has no parameters, or when a function doesn't return anything and so has no sensible return type. For example, the built-in function rand takes no parameters and so has prototype:

  int rand(void);
The built-in function srand doesn't return anything, so its prototype is:
  void srand(unsigned int);

Return to Table of Contents

Standard (Built-in) Functions

C compilers come with libraries of standard functions that can be used (called) by programs. Access to a library is gained by including the header file for the library. The header file contains prototypes for all of the functions in the library. These prototypes tell the compiler what a call of each of the functions should look like. For example, the file stdio.h contains prototypes for printf, scanf, ... So, putting:

#include <stdio.h>
tells the compiler the prototypes for these functions, and so lets you use the functions in your program. In UNIX, the standard header files for the C compiler are usually in /usr/include.

In some cases, the programmer must tell the compiler where to find the actual libraries. This is done with a flag. For example, whenever a program includes math.h, the -lm flag must be used:

gcc mathexample.c -lm
where mathexample.c contains the line:
#include <math.h>
IF the -lm flag is omitted, the compiler will complain that it can't find the definition of any of the math functions used in mathexample.c. In UNIX, the actual code for standard libraries is usually in /usr/lib.

Some Useful Standard Libraries (see pp. 72 - 82 and Appendix B)
header file to #includecontents of library
stdio.h input/output functions
math.h math functions
  • trigonometric functions
  • logarithms
  • exponentiation
  • ...
stdlib.h misc. functions
  • memory allocation
  • string to number conversions
  • some integer math functions
  • random number generation
ctype.h character functions (as int)
  • testing if a character is a letter, digit, ...
  • case conversions
string.h string manipulation functions

Again, remember that if math.h is used, the -lm flag must be used when compiling.

Return to Table of Contents

User Defined Functions

Example function definition:

int square(int arg)
  { /* compute and return the square
       of integer arg */
  int result;

  result = arg * arg;
  return (result);
  }

User defined functions are called just like standard functions. E.g.:

  printf("The square of %d is %d.\n",
         1 + 1, square(1 + 1));
See ~taw2/pub/square.c.

Functions that don't return anything don't use a return statement. E.g.:

void print_square(int arg)
  { /* print the square of arg */
  int result;

  result = arg * arg;
  printf("The square is %d.\n", result);
  }
  
  /* variable result can't be used here */

Local variables can only be used inside the function they are declared in. The scope of a variable declaration is the part of the program in which the variable can be used.

Return to Table of Contents

Program Format

Programs with user defined functions can be formatted in two different ways:

Because the compiler must see the definition or prototype of a function before it is called, the order of function definitions matters with this style.

A function definition can not appear inside the definition of another function.

Return to Table of Contents

Parameter Passing

C has two ways that parameters can be passed to functions. The value of pass-by-value parameters can not be changed from inside the called function, while the value of pass-by-address parameters can.

Pass-by-Value Parameters

Pass-by-value parameters are used only to provide input to a function - the function can not change the value of the parameter outside of the body of the function. E.g.:

int add2(int i)
  { /* return 2 + i */

  i = i + 2; /* this appears to change the value
                of i, but the change is only 
                visible in this function.  Such
                an assignment is considered 
                poor programming style. */
  return i;
  }

This code will not change the value of any variable used as a parameter in a call to add2. E.g.:

main () {
  int j = 7, k;

  k = add2(j);
  printf("The value of k is %d and");
  printf("the value of j is %d\n.", k, j);
}
This prints the value 9 for k and 7 for j. The value of j is not changed.

The name of the variable doesn't matter:

main () {
  int i = 5, k;

  k = add2(i);
  printf("The value of i is %d.\n", i);
}

Reason: the C compiler makes a copy of pass-by-value parameters for the function to use. So, any changes made to the parameter inside the function don't affect the value of the actual argument outside of the function.

Any parameter that isn't of an "address" type (discussed next) is a pass-by-value parameter.

Return to Table of Contents

Pass-by-Address Parameters

The value of a pass-by-address parameter can be changed by the function is is passed to.

style: always use pass-by-value parameters unless the function must change the value of the parameter

Pass-by-address parameters are easy to recognize because they are always of an address type. An address type is a normal C type followed by *. E.g.:

int *i;
declared i to have type address of int (or pointer to int). That is, the value of i is not an int - it is an address (or location) in the computer's memory.

Again, i does not refer to an integer, but instead to a spot in memory where an integer can be stored.

The value at some address is referred to using the * operator. E.g.: *i is the value stored at address i.

Example:

int add2(int *i) {
  /* this function modifies the value stored
     at address i by adding 2 to it, and also
     returns 2 + that value */

  *i = *i + 2; /* again, *i means the value stored
                  at address i */
  return (*i);
}

The * operator is used when referring to the value stored at some address on both sides of an assignment. The * operator is called the dereferencing operator.

Calling this version of function add2 will change the value of the parameter:

main () {
  int j = 7, k;

  k = add2(&j);
  printf("The value of k is %d");
  printf("and j is %d.\n", k, j);
}
The value 9 is printed for both variables.

Operator & is called the address operator. It finds the address where the value of a variable is stored.

If we pass a parameter to a function using &, the code in the function must use * to refer to the value of the parameter.

When a function needs to return 2 results, pass-by-address parameters are often used. See ~taw2/pub/sqcube.c for an example.

The & operator can only be applied to variables - not constants, expressions, or function calls! For example, &2 is illegal. The C compiler doesn't store the constant 2 in memory, so it doesn't have an address.

Consider a function that exchanges the values of 2 integer variables. As the value of both must be changed, both are pass-by-address parameters.

void swap(int *i, int *j) {
  /* this function exchanges the
     values of i and j */

  *i = *j;
  *j = *i;
}

Will this function work correctly?

No! Both variables will have the value previously stored at address j.

Fix:

void swap(int *i, int *j) {
  /* this function exchanges the
     values of i and j */

  int temp;

  temp = *i;
  *i = *j;
  *j = temp;
}
See ~taw2/pub/swap.c.

Example: A function that gets an integer from the user and puts its value into a pass-by-address parameter.

void getVal(int *val) {
  /* puts a user entered integer into val */

  printf("Enter an integer value: ");
  scanf(("%d", val);
}

Note that we don't use &val in the scanf because val is already the address of an integer! Using &val would be an error.

Return to Table of Contents

The return Statement

The return statement is used to indicate what value a function returns. It also causes control to leave the function and return to the place the function was called from. Example:

int inc(int i) {
  /* return i + 1 */

  return (i + 1);
  printf("This printf is never executed.\n");
} /* end inc */

A function can use multiple return statements:

double abs(double arg) {
  /* return the absolute value of arg */

  if (arg < 0)
    return (-arg);
  else
    return (arg);
}

Generally, using multiple returns in one function in poor style.

Fix:

double abs(double arg) {
  /* return the absolute value of arg */
  double res;

  if (arg < 0) res = -arg;
  else res = arg;
  return (res);
}

or:

double abs(double arg) {
  /* return the absolute value of arg */

  return ((arg < 0)?-arg:arg);
}

Return to Table of Contents

Scoping Rules

Recall that the scope of a variable is the region of a program in which a variable is defined. In C, the scope of a variable is the block it is declared in, where a block is just a region of a program enclosed in { }, or an entire file.

  if (num >= 0) {
    int counter;
    
    fact = 1;
    for (counter = 1; counter <= num; counter++)
      fact *= counter;
    }
    /* counter is not defined here */

Variables declared in the preamble (beginning of the program - before main) are global variables. Such variables are visible (can be seen and modified) (are defined) anywhere in the program (file).

Example:

#include <stdio.h>

float f1; /* global */

int fun1(int x);

main () {
  float f2; /* local to main */

  /* visible here: f1, f2 */
}

int fun1(int x) {
  float f3; /* local to fun1 */
  /* it is illegal to declare a 
     variable named x here. */

  /* visible here: x, f1, f3 */

  if (x < 7) {
    float f4; /* local to this block */

    /* visible here: x, f1, f3, f4 */
  }
}

If two variables with the same name are in scope (i.e. both are visible in the current scope) because they were declared in nested scopes, then the most local definition is that one that is visible.

#include <stdio.h>

int x = 3; /* global */

main () {
  float x = 4.0;

  /* here, x is a float and
     has value 4.0 */

  if (x < 3) {
    double x = 5.0;

    /* here, x is a double
       and has value 5.0 */
  }
}

Style notes:

Return to Table of Contents