Website Logo

5. Functions and Procedures

By Joker. February 14th, 2020. 10:10 AM

Check the guide's index here.
Objectives
  • Functions and procedures in C
  • Header and body of a function
  • Parameters
  • Passing basic type parameters
  • Return value (return)
  • The void "type"
  • Function prototypes
  • Local variables

Intro

Although we don't even know how to write a function, we've been using a ton of them in our many programs. Many examples are printf, scanf, getchar, putchar, etc., that make part of the standard C library and that are supplied to us whenever we acquire any C compiler.

In this chapter, we're going to learn how to write functions and procedures, how to pass parameters and how to return any value as the result from a function.

It's absolutely indispensable that a C programmer can absolutely master the writing of programs in a modular form, through procedures and functions.

Problem: Write a program that prints the following output on-screen, writing the 20 asterisk line through a for cycle.

********************
Numbers between 1 and 5
********************
1
2
3
4
5
********************
prog0501.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int i;
 5:   /* Writing the Header */
 6:   for(i=1; i<=20; i++)
 7:     putchar('*');
 8:   putchar('\n');
 9:
10:   puts("Numbers between 1 and 5");
11:
12:   for(i=1; i<=20; i++)
13:     putchar('*');
14:   putchar('\n');
15:
16:   /* Writing the numbers */
17:
18:   for(i=1; i<=5; i++)
19:     printf("%d\n", i);
20:
21:   for(i=1; i<=20; i++)
22:     putchar('*');
23:   putchar('\n');
24:   return 0;
25: }

How you can see, the set of used code to write the line of asterisks on screen

for(i=1; i<=20; i++)
  putchar('*');
putchar('\n');

is repeated three times. The ideal plan would be to write this piece of code once and invoke it every time it's necessary. The solution is to divide the program in small fragments of code, each of them being responsible for some determined task.

Problem: Write a program that writes a 20 asterisk line on-screen.

prog0502.c

1: #include <stdio.h>
2:
3: int main(){
4:   int i;
5:   for(i=1; i<=20; i++)
6:     putchar('*');
7:   putchar('\n');
8:   return 0;
9: }

The previous program places a 20 asterisk line on-screen. It was implemented as being the main. As its function is to write a line, instead of calling it main, we're going to call it line.

prog0502.c

1: #include <stdio.h>
2:
3: line(){
4:   int i;
5:   for(i=1; i<=20; i++)
6:     putchar('*');
7:   putchar('\n');
8:   return 0;
9: }

Now, if you try to make an executable with this code, you'll obtain a linking error, once the main function doesn't exist in the program.

Note: A C program ALWAYS has to possess a main() function in its code, independently from the number or variety of functions that the program contains.

What we've pretty much done was write the responsible code for printing the line on-screen. The fact that this function exists doesn't mean it may be executed, just as a worker at a restaurant only serves a certain dish once a client asks for it, although it exists in a restaurant's menu.

The main() (or any other function invoked by the main) will have to solicit the services of that function. This is done by writing the function's name with its respective parenthesis, just like we'd do with the getchar function or any other function.

line();

That way, the program prog0501.c could have been written like this:

prog0503.c

 1: #include <stdio.h>
 2:
 3: line(){
 4:   int i;
 5:   for(i=1; i<=20; i++)
 6:     putchar('*');
 7:   putchar('\n');
 8: }
 9:
10: int main(){
11:   int i;
12:
13:   line(); /* Writes one asterisk line */
14:   puts("Numbers between 1 and 5");
15:   line(); /* Writes another asterisk line */
16:   for(i=1; i<=5; i++)
17:     printf("%d\n", i);
18:   line(); /* Writes a final asterisk line */
19:   return 0;
20: }

In the presented code, the program possesses two functions written in the same file. The main function is responsible for starting the program and executing all of the instructions present inside. The line function is responsible by writing a line on-screen. Therefore, every time we want to write a line on-screen, we have to invoke the line() function, so you can avoid always having to write all of the code it executes.

This kind of approach has a lot of advantages. One of the better ones is that is that if, for some reason, it's necessary to alter the line (substitute the '*' with a '-' or write 30 characters instead of 20), you only have to alter the respective code once in the line function. If you didn't use a function, you'd need to alter all occurrences of the cycle responsible for writing on-screen.

Note: The declared variables inside of a block are local to that block, and are not recognized outside of it.

Note that the i variable (which is declared in both functions) is, therefore, local to both functions. What I mean by that is that they're both different variables that are written in the main and line functions, although their names are exactly the same.

A Function's Characteristics

  • Each function needs to have a unique name, which is used to invoke it anywhere on the program to which it belongs to.
  • A function can be invoked in other functions.
  • A function (like its name indicates), it must execute A SINGLE WELL DEFINED TASK.
  • A function must behave like a black box. It doesn't matter how it works, only that the final result is the expected one, with no collateral damage done.
  • A function's code must be as independent as possible from the rest of the program, and it must be as generic as possible, so it can be reused in other projects.
  • A function can receive parameters that alter its behavior in order to easily adapt to distinct situations.
  • A function can return, to the entity that invoked it, a value as a result of its work.

A Function's Name

The choice of a function's name obeys to the rules presented for variable designation (Chapter 2).

A function's name must be unique (it can't be the same name as another function or variable).

A function's name must specify both what the function really does, and should be easy to read and interpret (Ex: putchar vs. p23_k45_char).

How a Function Works

  • A function's code is only executed when it is invoked in some part of the program to which it is, in some way, linked to.
  • Every time a function is invoked, the program that invokes it is temporarily "suspended". Next, the instructions inside the function body are executed. Once the function finishes its work, the program's execution control goes back to the place where it was invoked.
  • The program that invokes a function can send Arguments, which are received by tehe function. These are received and stored in local variables, which are automatically initiated with the sent values. We call these variables Parameters.
  • After it finishes its instructions, a function can return a value to the program that invoked it.

Problem: Write a program that, making use of three distinct functions, writes on-screen the following output:

***
*****
*******
*****
***

prog0504.c

 1: #include <stdio.h>
 2:
 3: line3x(){
 4:   int i;
 5:   for(i=1; i<=3; i++)
 6:     putchar('*');
 7:   putchar('\n');
 8: }
 9:
10: line5x(){
11:   int i;
12:   for(i=1; i<=5; i++)
13:     putchar('*');
14:   putchar('\n');
15: }
16:
17: line7x(){
18:   int i;
19:   for(i=1; i<=7; i++)
20:     putchar('*');
21:   putchar('\n');
22: }
23:
24: int main(){
25:   line3x();
26:   line5x();
27:   line7x();
28:   line5x();
29:   line3x();
30:   return 0;
31: }

For the execution of program prog0504.c, we wrote 4 functions:

  • line3x: function responsible for writing 3 asterisks on screen
  • line5x: function responsible for writing 5 asterisks on screen
  • line7x: function responsible for writing 7 asterisks on screen
  • main: function that invokes all other functions

Note that the function names are sets of characters with no real meaning for the compiler. The fact that 3x, 5x or 7x appear in the function names serves only to read the program easily, so that the reader can easily understand what each of the functions does (line5x: makes a line with five asterisks).

Since the asterisk is not an allowed character in function names, the taken option was to use an 'x' to represent it in the function name.

If we look with some attention to the code in the three functions, we'll verify it is, in all parts, equal, except for the following lines:

 5:   for(i=1; i<=3; i++)
12:   for(i=1; i<=5; i++)
19:   for(i=1; i<=7; i++)

which correspond to the number of asterisks to be printed on-screen.

The ideal would be to write a single line function, responsible for writing a line on-screen, with a specific number of asterisks on each call. In this case, we want to tell the function what's the number of characters to be placed on-screen.

  • If we want to write three asterisks, we invoke the function line(3).
  • If we want to write five asterisks, we invoke the function line(5).
  • If we want to write 123 asterisks, we invoke the function line(123).

The function is always the same (line function), what changes is the number of characters to be printed on screen.

In the previous program, our main() would become:

24: int main(){
25:   line(3);
26:   line(5);
27:   line(7);
28:   line(5);
29:   line(3);
30:   return 0;
31: }

How would we write the line function, then?

The line function receives an integer type value inbetween parenthesis, which will have to be placed in a variable.

After storing that value, the line function's cycle will execute the number of times stored in that variable.

What's the name of the function being written?

line()

How many parameters will it receive?

1

What's the parameter's type?

integer

Choose a name for the variable that will store that parameter

num

The function's header will look like this

line(int num) /* The function receives an integer that it stores in num */

And the function's body?

All we need to do is alter the cycle's control condition to

for(i=1; i<=num; i++)

Therefore, the previous program could have been written like this:

prog0505.c

 1: #include <stdio.h>
 2:
 3: line(int num){
 4:   int i;
 5:   for(i=1; i<=num; i++)
 6:     putchar('*');
 7:   putchar('\n');
 8: }
 9:
10: int main(){
11:   line(3);
12:   line(5);
13:   line(7);
14:   line(5);
15:   line(3);
16:   return 0;
17: }

Parameters

Communicating with a function is done through arguments that are sent to it and the parameters present in the function that receives them.

The number of parameters in a function can be 0, 1, 2, etc., depending only of the programmers' necessities.

Each function needs, however, what's the type for each of the parameters.

Note: Any data type from the language can be sent as a parameter into a function, even data types defined by the programmer themself.

A function's parameters are separated by a comma, and it is absolutely necessary to, for each of them, indicate their type.

function(int x, char y, float k, double xpto)

What was previously referred is still true for function parameters of the same type.

function(int x, int y, int k, int xpto)   /* Correct Example   */
function(int x, y, k, xpto)               /* Incorrect Example */
Note: A parameter is nothing more than a variable local to the function to which it belongs. A parameter is automatically initiated with the value send by the invoking program.

Passing arguments into a function should be done inbetween parenthesis, separated by commas, immediately after the function's name.

When you call a function, the number and type of the sent arguments must be coincident with the parameters present in the function header.

In the next example, function receives three parameters that it stores in three variables named ch (char), n (int) and x (float). The variables ch, n and x are automatically initiated with the values 'A', 123 and 23.456, respectively, which are sent to it from main.

main(){
  ...
  funtion('A', 123, 23.456);
  ...
}

function(char ch, int n, float x){
  ...
}
Note: The number of parameters sent to a function must be the same as the number of existent parameters in the function header. The parameter types must also correspond each.

If the function receives more than one parameter, the sent arguments are associated to the function parameters in the order they're written in.

Note: It's common to call parameter both to a function's summoning arguments as well as to the real function parameters.
Note: Any valid expression in C can be sent as an argument into a function.

Problem: Alter the previous program so that the line function can write any character, not just the asterisk character.

prog0506.c

 1: #include <stdio.h>
 2:
 3: line(int num, char ch){
 4:   int i;
 5:   for(i=1; i<=num; i++)
 6:     putchar(ch);
 7:   putchar('\n');
 8: }
 9:
10: int main(){
11:   line(3, '+');
12:   line(5, '+');
13:   line(7, '-');
14:   line(5, '*');
15:   line(3, '*');
16:   return 0;
17: }

$ ./prog0506
+++
+++++
-------
*****
***
$

Adding a new parameter to the function allows us to print a line with any character that we now have to pass in the function call.

Note: The name of variables (parameters) present in a function's header is totally independent from the name of the variables that are sent by the program that is invoking it.

The Function's Body

A function's body is composed of C instructions in accordance with the language's syntax. It has to be immediately after the function's header, and is written between curly brackets.

Note: A function's header should NEVER be followed by a semi-colon (;)

Every time a function is invoked by the program, the function's body is executed, one instruction at a time, until the function's body ends or until the return instruction is reached, returning immediately to the program in the spot where it was invoked.

Note: Inside the function's body, you can write any instruction or set of instructions in C. Be warned though - you cannot define functions inside other functions in C.

Any instruction is allowed inside of a function (ex.: attributions, if, for, switch, ..., invoke other functions, etc.)

The number of instructions that can be inside of a function has no limits. It should be, however, relatively small and responsible for executing a single task.

return

The return instruction allows to end a function's execution and return to the program that invoked it.

Executing the return instruction inside of main makes the program end.

Example:

#include <stdio.h>
int main(){
  printf("Hello");      /* Write Hello                         */
  return 0;             /* Ends the function (and the program) */
  printf(" World\n");   /* This line is never executed         */
}

Functions that Return a Value

It's possible that a function is responsible for executing a determined task and that, once that task is finished, it returns ONE SINGLE result. This result can be stored in a variable to be used in any other instruction.

Returning a result is done through the return instruction, followed by the value to be returned.

Note: Any expression valid in C can be placed after the return instruction.

Let's suppose we want to calculate the sum between two integer numbers. The function responsible for this task should have the ability to make the sum and return its result.

n = sum(3, 4);
printf("%d\n", n);      /* 7 */

Notice that the sum function will have to receive two integers and will have to return an integer result, which corresponds to the sum of the two parameters received by the function.

int sum(int x, int y){
  int res;
  res = x + y;
  return res;
}

Notice that the sum of x with y is placed in an integer type variable, which is, afterwards, returned as the function's result.

Problem: Write a program that asks the user for two numbers and prints out the result of their sum and their doubles on screen.

prog0507.c

 1: #include <stdio.h>
 2: /* Returns the sum of two integers */
 3: int sum(int a, int b){
 4:   return a + b;
 5: }
 6:
 7: /* Returns the double of any integer */
 8: int duplo(int x){
 9:   return 2 * x;
10: }
11:
12: int main(){
13:   int n, i, total;
14:   printf("Insert two numbers: ");
15:   scanf("%d%d", &n, &i);
16:   total = sum(n, i); /* Attrib. the function's result to a var */
17:   printf("%d + %d = %d\n", n, i, total);
18:   printf("2 * %d = %d and 2 * %d = %d\n", n, duplo(n), i, duplo(i));
19:   return 0;
20: }

$ ./prog0507
Insert two numbers: 4 7
4 + 7 = 11
2 * 4 = 8 and 2 * 7 = 14
$

Since the duplo function returns an integer, its result can be used inside of a printf, being written with the integer format (%d).

Note: A function can be invoked inside of another function. The result is the same that would be obtained if, instead of the function call, we wrote the result obtained from it.

Problem: What's the output of the following line?

printf("%d", duplo(sum(duplo(2),3)));

duplo(2) -> 4
sum(duplo(2),3) -> sum(4,3) -> 7
duplo(sum(duplo(2),3)) -> duplo(sum(4,3)) -> duplo(7) -> 14

therefore, the value printed on screen would be 14.

The previous execution is equivalent to:

a = duplo(2);
b = sum(a,3);
c = duplo(b);
printf("%d",c);
Note: A function can contain various return instructions. However, only one return is executed per function.

Example: Write a function that calculates which of two integers is the largest.

int max(int n1, int n2){
  if(n1 > n2)
    return n1;
  else
    return n2;
}

Notice that if the function receives two integers, then the bigger one is certainly an integer, so the function has to return an int type value.

Functions and Procedures

In the beginning of this chapter, we talked about procedures and functions. However, throught the whole chapter we only talked about functions. So, what about procedures?

In C, there are only functions, while in other languages, there are two distinct types of routines - functions and procedures. A good example would be PASCAL, which allows the definition of functions using the reserved word FUNCTION, and allows the definition of procedures through the reserved word PROCEDURE.

So, what is the difference between a function and a procedure?

Note: A function always has an associated type and returned value, while a procedure returns no value.

As an example of Function we have the function int max(int n1, int n2), which verifies what's the largest integer out of the two and returns one of them.

As an example of Procedure, we have the line() function, which prints out a line on-screen and then ends without returning any values.

Note: Every time the return value is not placed on a function, it's replaced by the int type.

That way, writing

line(int n)

is the same as writing

int line(int n)

The way to invoke functions and procedures is also different. Since there is no return value, a procedure is invoked like its a simple instruction.

line(12);

As for functions, these can be invoked in three distinct ways:

  • Inside of an attribution, to store a value inside of a variable.

    x = sum(23,y)+duplo(k+2);
  • Inside of a function, where the return value is taken as another function's parameter.

    printf("%d %d", duplo(5), sum(duplo(2), 3+2));
    /* or */
    if(sum(x,y) > 0)
      ...
  • Just like you would invoke a procedure. In this case, the return value is sent into the void.

    getchar; /* Stop the screen. It does not matter what char is written */

The void "type"

In C, there is a reserved word - void - that allows us to indicate that a function does not return any values of any data type.

That way, the previously presented line() function should have been written like this:

prog0506.c

 1: #include <stdio.h>
 2:
 3: void line(int num, char ch){
 4:   int i;
 5:   for(i=1; i<=num; i++)
 6:     putchar(ch);
 7:   putchar('\n');
 8: }
 9:
10: int main(){
11:   line(3, '+');
12:   line(5, '+');
13:   line(7, '-');
14:   line(5, '*');
15:   line(3, '*');
16:   return 0;
17: }

All of the remaining code remains unaltered.

It's also usual to find the reserved word void to indicate that a function receives no parameters.

For example: the two next strips of code are pretty much the same.

void line(){
  int i;
  for(i=1; i<=20; i++)
    putchar('*');
  putchar('\n');
}
void line(void){
  int i;
  for(i=1; i<=20; i++)
    putchar('*');
  putchar('\n');
}
Note: A function that "returns void" is normally called a Procedure.

Every time we want to exit a procedure, we use the return instruction with no values or expressions.

Note: The return instruction ends the function (procedure) execution, and the program control is returned back where the function (procedure) was invoked.

There are some programmers that reinforce in a function's definition that it does not receive any arguments by putting void between parenthesis. They write the func() function like func(void). Try adopting that habit if you want to.

Where to Place Functions

In C, functions can be placed anywhere in an archive (for now, there's only one inside a single archive), before or after being invoked, before or after being used in the main function. There is, hwoever, a restriction that should be accounted for.

Let's use the following program as an example:

prog0508.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   line();
 5:   printf("Hello World\n");
 6:   line();
 7:   return 0;
 8: }
 9:
10: void line(){
11:   int i;
12:   for(i=1; i<=20; i++)
13:     putchar('*');
14:   putchar('\n');
15: }

Now, although our code is formally well written, we're going to get a compilation error similar to "function line: redefinition".

How is it possible that the line function is redefined, since it's only defined once (lines 10: to 15:)?

Well, the compilation process is, in general, a sequential process, that goes through the entire archive where the code is, from the beginning until the end. So it makes a syntax verification and, every time everything's fine, it creates the archive with the object code.

Now, since the line function is defined after the main function, the compiler only knows that its header is when it's compiling it. However, the line function is invoked by the main function. Since, when the main function is compiled, the compiler still doesn't know it, it creates a prototype (header) for the line function similar to

int line();

When it finds the line function (with the header void line()), the compiler thinks it's two distinct functions, (because they return different types) but with the same name. Since there can't be two functions with the same name, the compiler presents the corresponding error and terminates the compiling process.

Notice, though, that if the line function returned an integer, there would be no problems.

So, how do we solve this problem?

The solution is to show the compiler the header (interface) of our function, declaring the function in the same way we declare variables.

Declaring functions consists in writing its header followed by a semi-colon (;) and it must be done before using the function we want to declare. Traditionally, declarations are done immediately after our #includes.

prog0508.c

 1: #include <stdio.h>
 2:
 3: void line(); /* line function declaration */
 4:
 5: int main(){
 6:   line();
 7:   printf("Hello World\n");
 8:   line();
 9:   return 0;
10: }
11:
12: void line(){
13:   int i;
14:   for(i=1; i<=20; i++)
15:     putchar('*');
16:   putchar('\n');
17: }
Note: A function's prototype corresponds to its header followed by a semi-colon (;). This identifies the whole function structure (name, parameters and return type).

A good programming habit is to always place your function prototypes at start of your program, so that you indicate the compiler what's the structure of functions that are going to be used by the program. That way, the compiler can verify, in each function call, if it was correctly implemented.

Note: A certain function's prototype should be placed just before its call. However, it is a good coding habit to always place your manually defined function headers immediately before the first function's code.
#include <.....>
void f1(int n, char ch); /* function prototype */
int max(int n1, int n2);
main(){
  .....
}
void f1(int n, char ch){
  .....
}
int max(int n1, int n2){
  .....
}
Note: Although it seems the goal of writing a function's prototype is to indicate the compiler what are its parameters, in reality, all the compiler needs to know is what the return type for that function is.

So, the next lines have the same functionality for the compiler.

int max(int n1, int n2);
int max(int , int);
int max();

Local Variables

As was already previously observed, variables can be declared inside the body of a function. These variables are only visible (i.e., recognized) inside of the function itself.

Note: Variables declared inside of a function are only recognized inside of that function. Those are, because of that, called local variables.

Declaring variables inside of a function should be done before any other instruction.

function( ............){
  variable_declaration;
  instructions;
}

These variables can only be used inside of the function they were declared in.

If one equal variable is declared in two distinct functions, there will be no sort of problem, because the compiler knows which variable to use in each function. Although they have the same name, they're distinct variables with no relation to each other, in the same way that there can be two individuals named Joseph in distinct rooms.

Note: After finishing the execution of a certain function, all of its local variables are destroyed.
Note: Every time it is possible, please make use of local variables, this way avoiding collateral effects that occur when you use global variables.

Final Observations

Since the C function does not possess, in its structure, a specific data type to represent TRUE and FALSE, it is necessary that functions that have to return a logic value as a result are declared with an int type return.

However, coding these functions is normally not done correctly. Notice the next example:

Problem: Write the isequal function, which returns if two integers are equal or not and returns the corresponding result.

int isequal(int x, int y){
  if(x == y)
    return 1;
  else
    return 0;
}

Notice that the function only has to compare the values of x and y and return 1 or 0.

But why return 1 when the result is True, and not -1 or -435 or 2345, which also represent the logic value True.

The answer is that, when questions like these haunt our souls, it's a sign that something is not well coded.

The function's goal is to verify if the values of x and y are the same. This is done through the comparison x == y. Now, this comparison returns, by itself, a logic value that indicates if x is equal to y or not.

Therefore, what we want is to return the logical value that results from the x == y test, because

x == yreturn
x = 5, y = 5TrueTrue
x = 7, y = 5FalseFalse

As can be observed through the presented table, the analysis value (x == y) corresponds to the value of what we want to return as the function's result.

Thus, the previous function should have been written like this:

int isequal(int x, int y){
  return (x == y);
}

returning the result of comparing x with y.

< 4. Cycles

6. Strings >

Check These Articles