Functions

It's All About Functions

Some of the first statements made during this course were these: And the general form of a C program was shown. (The parts in bold are the subject of this topic)
include files

function declarations (prototypes)

data declarations (global)

main function header
{
  data declarations (local)
  statements
}

other functions
Although we've actually only written one function so far (main), we've used two others: scanf and printf. These functions are in the C standard library.

A function has the form:

return_type function_name(formal_parameters)
{
  function_body
}
Explanations:
Function part Description
return_type This describes the type of the data that the function will return. Almost all data types are allowed to be returned from a function. If a function returns data, then the function can be used in an expression, otherwise, it can't.
function_name The name of the function. The name must follow the same rules that an identifier must follow.
formal_parameters This is an optional comma-separated list of values that will be passed to the function. A function that takes 0 parameters will have the keyword void as the only item in the list. (Students that still think that function(void) is the same as function() are required to read this again.)
function_body The body consists of all of the declarations and executable code for the function. If the function is defined to return a value, there must be at least one return statement in the body. (There can be multiple return statements.)
Here's an example of a user-defined function:
float average(float a, float b)
{
  return (a + b) / 2;
}
The function above meets the requirements:
  1. The function is named average.
  2. It will return a value of type float.
  3. It expects to be passed two values, both of type float. (Note that you can't do this for the parameters: float a, b)

A complete program using our new function:

#include <stdio.h>

float average(float a, float b)
{
  return (a + b) / 2;
}

int main(void)
{
  float x;
  float y;
  float ave;
  
  x = 10; 
  y = 20;
  ave = average(x, y);
  printf("Average of %g and %g is %g\n", x, y, ave);

  x = 7; 
  y = 10;
  ave = average(x, y);
  printf("Average of %g and %g is %g\n", x, y, ave);
  
  return 0;
}
The output:
Average of 10 and 20 is 15
Average of 7 and 10 is 8.5
Both the return value and the parameters are optional:
/* No return, no parameters */
void say_hello(void)
{
  printf("Hello!\n");
  return; /* optional */
}
/* No return, one parameter */
void say_hello_alot(int count)
{
  int i;
  for (i = 0; i < count; i++)
    printf("Hello!\n");
}
/* Return, no parameters */
float pi(void)
{
  return 3.14159F; 
}
Calling the functions:
int main(void)
{
  float p;

  say_hello();       /* no arguments, needs parentheses */
  say_hello_alot(5); /* one argument                    */
  p = pi();          /* no arguments                    */
  p = pi;            /* Error, parentheses are required */

  return 0;         /*    not optional                 */
}

Function Prototypes

Note the ordering of the two functions (main and average) in this program: (foo.c)
 1. #include <stdio.h>
 2.
 3. int main(void)
 4. {
 5.   printf("ave = %.2f\n", average(10, 20));
 6.   return 0;
 7. }
 8. 
 9. float average(float a, float b)
10. {
11.   return (a + b) / 2;
12. }
There are several problems that the compiler complains about and you probably don't have a clue what some of them mean:
foo.c: In function `main':
foo.c:5: warning: implicit declaration of function `average'
foo.c:5: warning: double format, different type arg (arg 2)
foo.c: At top level:
foo.c:10: error: conflicting types for 'average'
foo.c:5: error: previous implicit declaration of 'average' was here
New compilers give more information about the problems:
warn.c: In function ‘main’:
warn.c:5:3: warning: implicit declaration of function ‘average’ [-Wimplicit-function-declaration]
   printf("ave = %.2f\n", average(10, 20));
   ^
warn.c:5:3: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
warn.c: At top level:
warn.c:9:7: error: conflicting types for ‘average’
 float average(float a, float b)
       ^
warn.c:5:26: note: previous implicit declaration of ‘average’ was here
   printf("ave = %.2f\n", average(10, 20));
                          ^
Some compilers are even better at describing the problem:
warn.c:5:26: warning: implicit declaration of function 'average' [-Wimplicit-function-declaration]
  printf("ave = %.2f\n", average(10, 20));
                         ^
warn.c:5:26: warning: format specifies type 'double' but the argument has type 'int' [-Wformat]
  printf("ave = %.2f\n", average(10, 20));
                ~~~~     ^~~~~~~~~~~~~~~
                %.2d
warn.c:9:7: error: conflicting types for 'average'
float average(float a, float b)
      ^
warn.c:5:26: note: previous implicit declaration is here
  printf("ave = %.2f\n", average(10, 20));
                         ^
2 warnings and 1 error generated.
Some compilers treat this situation as a warning and not an error. Borland's compiler and GNU's gcc both treat it as a error, so you can't write code like the above. Sadly, Microsoft's compiler doesn't catch the error and when you run the above program, you get this:
ave = 796482944349676280000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000.00
Removing the include file from this program yields a similar warning:
/*#include <stdio.h>*/

int main(void)
{
  printf("Hello!\n");
  return 0;
}
Warnings:
foo.c: In function `main':
foo.c:6: warning: implicit declaration of function `printf'
Just like all other identifiers in C, we must tell the compiler about our functions before we use (call) them. We do this with a function declaration, also called a function prototype.

This program is now perfectly fine:

#include <stdio.h> /* For printf */

/* Function prototype, notice the semicolon    */
/* Prototypes do not have curly braces or body */
float average(float a, float b);

int main(void)
{
  /* Both printf and average are known to the compiler */
  printf("ave = %.2f\n", average(10, 20));
  return 0;
}

/* Function definition, no semicolon    */
/* Definition has curly braces and body */
float average(float a, float b)
{
  return (a + b) / 2;
}
A function prototype introduces the identifier to the compiler. (Mr. Compiler, this is Mr. Identifier.) It tells the compiler that: Now that the compiler knows what average is, when it sees average later in the program, it will be able to make sure that it is used properly.
/* Prototype */
float average(float a, float b);

int main(void)
{
    /* Define some variables */
  int i;
  float f1, f2, f3, f4;
  double d1, d2;

    /* Set some values */
  f1 = 3.14F;  f2 = 5.893F;  f3 = 8.5F;
  d1 = 3.14;  d2 = 5.893;

    /* Call the function in various ways */
    /* Nothing wrong with these calls    */
  f4 = average(f1, f2);
  f4 = average(5.0F, 6.0F);
  f4 = average(10, 20);
  average(f1, f2);

    /* Potential problems when these execute */
  i = average(f1, f2);
  f4 = average(d1, d2);
  f4 = average(3.14, 9.1F);

    /* Fatal errors, compiler can't continue */
  f4 = average(f2);
  f4 = average(f1, f2, f3);

  return 0;
}
Detailed warning/error messages from the compiler:
Function callsOk   Warnings   Errors
f4 = average(f1, f2);
f4 = average(5.0F, 6.0F);
f4 = average(10, 20);
average(f1, f2);
Ok
Ok
Ok
Ok. You can ignore return values from functions.
i = average(f1,f2);
f4 = average(d1, d2);
f4 = average(3.14, 9.1F);
warning: conversion from 'float' to 'int', possible loss of data
warning: conversion from 'double' to 'float', possible loss of data
warning: conversion from 'double' to 'float', possible loss of data
f4 = average(f2);
f4 = average(f1, f2, f3);
error: too few arguments to function `average'
error: too many arguments to function `average'
Not all compilers will warn about the potential loss of precision, but they will all emit errors for the last two. The warnings above are from Microsoft's C compiler, version 7.1. These errors below are from gcc. If you invoke gcc with an additional command line switch:
gcc -Wconversion foo.c
You will see lots of additional warnings, which are informative, but not dangerous in this case:
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as floating rather than integer due to prototype
warning: passing arg 2 of `average' as floating rather than integer due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
error: too few arguments to function `average'
warning: passing arg 1 of `average' as `float' rather than `double' due to prototype
warning: passing arg 2 of `average' as `float' rather than `double' due to prototype
error: too many arguments to function `average'
warning: passing arg 1 of `average' as floating rather than integer due to prototype
warning: passing arg 2 of `average' as floating rather than integer due to prototype

Quick check: Does the following program compile?

float average(float a, float b);

int main(void)
{
  float f1, f2, f3;
  f2 = 3.14f;
  f3 = 5.0f;

  f1 = average(f2, f3);

  return 0;
}
To help answer this question, refer to the diagram. If you compiled this program like this: (assume the name of the file is main.c)
gcc -c main.c
or even like this:
gcc -Wall -Wextra -ansi -pedantic -c main.c
It will compile cleanly without any warnings or errors and you'll be left with an object file called main.o, which will be about 875 bytes in size.
E:\Data\Courses\Notes\CS120\Code\Functions>dir main.o
 Volume in drive E has no label.
 Volume Serial Number is 5852-DBD2

 Directory of E:\Data\Courses\Notes\CS120\Code\Functions

10/01/2016  08:54a                 875 main.o
               1 File(s)            875 bytes
               0 Dir(s)   6,154,678,272 bytes free

Now, if you try to link the file:

gcc main.o
You'll see this helpful error message:
main.o:main.c:(.text+0xb7): undefined reference to `_average'
collect2: ld returned 1 exit status
The error message (from the linker) is saying to you:
"Hey, you told me that this program needs to call a function called average, but I can't find it anywhere. I give up.".
Note that you would have received the same error had you attempted to compile and link with one command (removing the -c, which means to compile but don't link):
gcc main.c

Please make very sure that you understand the difference between compiling and linking. We will see many more situations where the code will compile properly, but not link.

Tracing Function Calls

What is the sequence of function calls made by the program below. Specify the function being called and its parameters.

void FnB(void)
{
  printf("tied the room ");
}

void FnC(void)
{
  printf("together\n");
}

void FnA(void)
{
  printf("That rug really ");
  FnB();
}

void FnD(void)
{
  FnA();
  FnC();
}

int main(void)
{
  FnD();
  return 0;
}

The sequence is this:

main(void);
FnD(void);
FnA(void);
printf("That rug really ");
FnB(void);
printf("tied the room ");
FnC(void);
printf("together\n");
What is the output of the program?

Pass By Value

In C, all function arguments are passed by value. In a nutshell, this means that any changes made to the parameters in the body of the function will not affect the values at the call site. An example will clarify:

#include <stdio.h> /* printf */
	
void fn(int x)
{
  printf("In fn, before assignment, x is %i\n", x);
  
  x = 10;
  
  printf("In fn, after assignment, x is %i\n", x);
}

int main(void)
{
  int i;
  i = 5;

  printf("Before call: i is %i\n", i); 
  
  fn(i); /* call site */
  
  printf("After call: i is %i\n", i);
  
  return 0;
}
Output:
Before call: i is 5
In fn, before assignment, x is 5
In fn, after assignment, x is 10
After call: i is 5

A copy of the value is passed to the function, so any changes made are made to the copy, not the original value. Visually, the process looks something like this:

When main begins, i is undefined:
Next statement, i is assigned a value:
The function is called and a copy of the argument is made:
The parameter, x, is assigned a value:
The function returns back to main and the original value is preserved:
Revisiting an example. We can use the parameter (modify it) without worrying that we will be changing something in another part of the program:

for loopwhile loop (no extra variable)while loop (more compact)
 
void say_hello_alot(int count)
{
  int i;
  for (i = 0; i < count; i++)
    printf("Hello!\n");
}
void say_hello_alot(int count)
{
  while (count)
  {
    printf("Hello!\n");
    count--;
  }
}
void say_hello_alot(int count)
{
  while (count--)
    printf("Hello!\n");
}

Functions and Scope

What happens when we have a variable named A in two different functions? Does the compiler or program get confused?
void fn1(void)
{
  int A;
  /* statements */
}
void fn2(void)
{
  int A;
  /* statements */
}
Declaring/defining variables inside of a function makes them local to that function. No other function can see them or access them.

Example:

#include <stdio.h> /* printf */

/* Function prototypes */
int add3(int i, int j, int k);
int mult3(int p, int q, int r);

int main(void)
{
  int a = 2;
  int b = 3;
  int c = 4;
  int d;
  int e;

  d = add3(a, b, c);
  e = mult3(a, b, c);
}

int add3(int x, int y, int z)
{
  int a = x + y + z;
  return a;
}

int mult3(int a, int b, int c)
{
  int x = a * b * c;
  return x;
}

Technically, curly braces define a scope. This means that any time you have a pair of curly braces, a new scope is created. So, yes, this means that compound statements within loops (for, while, etc.) and conditionals (if, etc.) are in a different scope than the statements outside of the curly braces. More on scopes later... Also, it is very important that you understand the difference between formal parameters and actual parameters (arguments) and the difference between parameter names in the function prototype vs. the names in the function definition.

The return Statement

We've already seen the return statement many times so far. It is used when we want to leave (i.e. return from) a function.

The general form is:

return expression ;
These functions both have illegal return statements:
int f1(int a, int b)
{
  /* statements */

  /* Illegal, must return an integer */
  return;
}
void f2(int a, int b)
{
  /* statements */

  /* Illegal, can't return anything */
  return 0;
}
These are the errors:
In function `f1': warning: `return' with no value, in function returning non-void
In function `f2': warning: `return' with a value, in function returning void

Divide and Conquer

The main point of using functions is to break the program into smaller, more manageable pieces. The technique is called Divide and Conquer and can be visualized like this:

The idea is that it will be easier to work individually on smaller parts of the whole problem, rather than try to solve the entire problem at once.

More on Scope

The scope or visibility of an identifier is what determines in which parts of the program the identifier can be seen.

This contrived example shows three different scopes. (The curly braces for each scope are highlighted.)

All statements are legalSome statements are illegal
void f1(int param) /* scope of param starts here */
{
  int a; /* scope of a starts here */
  int b; /* scope of b starts here */
  int c; /* scope of c starts here */

  /* do something with a, b, and c */

  while (a < 10)
  {
    int x; /* scope of x starts here */
    int y; /* scope of y starts here */

    if (b == 5)
    {
      int p; /* scope of p starts here */
      int q; /* scope of q starts here */

      p = a;         /* Ok, both in scope */
      q = x + param; /* Ok, all in scope  */

    } /* scope of p and q ends here */

    x = a; /* Ok, both in scope      */
    y = b; /* Ok, both in scope      */

  } /* scope of x and y ends here   */

} /* scope for a, b, c, and param ends here */
void f2(int param) /* scope of param starts here */
{
  int a; /* scope of a starts here */
  int b; /* scope of b starts here */
  int c; /* scope of c starts here */

  /* do something with a, b, and c */

  while (a < 10)
  {
    int x; /* scope of x starts here */
    int y; /* scope of y starts here */

    if (b == 5)
    {
      int p; /* scope of p starts here */
      int q; /* scope of q starts here */

    } /* scope of p and q ends here  */

    x = p; /* Error, p not in scope  */
    y = q; /* Error, q not in scope  */

  } /* scope of x and y ends here    */

  a = x; /* Error, x not in scope  */
  b = p; /* Error, p not in scope  */

} /* scope for a, b, c, and param ends here */

Notes about variables in block scope: (local variables)

Notes about global variables: