Functions in C

Functions in C are blocks of code that have a name and we can execute them, using that name. In C functions(also called routines) can accept arguments and return a value.

As usual, let's start by giving an example. You have already created a function - the main(). Remember? It was a special case and that is always the start of any program.



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

In this example:

  • The name of the function is main
  • int means that main() returns a value of type int
  • (void) is the list of arguments. It means that this routine does not accept arguments
  • Everything between the curly brackets {} is the body. This is the code that will be executed.
  • The return statement ends the execution of the function and returns a value to the point of invocation.

Functions in C consist of two parts:

  • Prototype
  • Body

Also, functions in C need to be:

  • Declared and..
  • Defined

Let's take a look at each of these concepts!

Prototype

The prototype of a function in C consists of its:

    <return type> <name>([list of parameters])

The prototype identifies a function. As you will see later in section "overloading", we can create several functions with the same name if they accept different arguments. So, to uniquely identify a routine we need its entire prototype. We need to write the prototype twice - when we declare it and when we define it.

Return type

This could be any valid data type, including void. When you declare that a routine returns value you must return a value of that type when it finishes. If you don't want to return a value, declare the function to be void. To return a value, use the return keyword inside the body.

Name

Functions in C have a name. We use it to execute the body of the function. When we do that, we say that we are calling that function.

Body

The body is just like any other block of code in C. You can perform the actions that you need and use the return statement to end the execution of the function and return a value. Note that if you declare the function to return a value, all paths in the body must return a value. Take a look at the next example. It seems that this innocent little function is OK, but it is not. Can you find the problem?

int returnMax(int num1, int num2)
{
    if(num1 > num2)
        return num1;
    if(num2 > num1)
        return num2;
}

What happens if the two numbers are equal? Neither the first, nor the second if will be true, so our function will not return anything, yet it was declared to return an int. To correct this we need to handle all possible cases.

    Functions in C must always return a value or never return a value(if they were defined as void).

We can do that in several ways, I choose to use an else statement to return num2 if the first condition is false. After all, if num1 is not greater we can safely return num2, because it is either bigger and we will return the correct result, or the numbers are equal and it doesn't matter which one we return.

int returnMax(int num1, int num2)
{
    if(num1 > num2)
        return num1;
    else
        return num2;
}

You can also end the execution of a void function by calling return, followed by semicolon without specifying a return value:

void someFunction()
{
    ...some code
    
    if(some condition)
        return;
        
    ... some more code
    
}

In this case when the condition of the if statement is met, the return statement will end the execution of this function and the "...some more code" stuff will not be executed.

Declaration and Definition

In most programming languages there is a difference between declaring a function and defining it. Functions in C need to be both declared and defined before we can use them. This is important note if you come from a language like Java or C# where you don't need to do the declaration.

Declaration

Declaration means that we declare that this function exists. In C, we that by writing down its prototype.

void printHello();

Usually we put all function declarations in one place in the top of the current file, just below the preprocessor directives like #include. Usually we keep all declarations (functions and variables) in the header files and the source file includes the headers that it needs.

Definition

Defining a function means giving it a body. We do that in the source file.

void printMessage()
{
    printf("Farewell, you traveler through the land of functions in C!\n");
    printf("May the forces of evil get confused on the way to your house!");
}

How do we call functions in C?

Function can be called only from within a function. To do that write down the name of the function that you want to call,
followed by parenthesis. Here is an example where we call "printMessage()":

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

Call with arguments

We have already called a function that accepts arguments - printf(), so this is nothing new for you. As an argument we can use any expression that returns a value:

  • a value literal
printf("This is a string literal.");
  • a variable
    float price = basePrice + basePrice * VAT;
    updatePrice(price);
  • a complex expression:
goDistance(v0 * t0 + (a * t * t) / 2);

What will happen here is that first the expression is calculated and the final result is passed as an argument to the function.

Note: When you call a function, you must supply the correct arguments. If the function is declared to accept one int and one float
argument then you need to provide the same number of arguments in the same order. We use a comma to separate the arguments. Here is
one example:

#include <stdio.h>
int findMax(int num1, int num2);

int main(void)
{
    int num1, num2, max;
    scanf("%d%d", &num1, &num2);
    max = findMax(num1, num2);
    printf("The bigger number is %d.", max);
    
    return 0;
}

int findMax(int num1, int num2)
{
    int result = num1;
    if(num2 > num1)
        result = num2;
    return result;
}

In the example above we call max() that finds and returns the bigger of two numbers. As you see, we can catch and use the returned
value. In this case we assign it to a new variable - max.

Local variables

When you call functions in C with arguments two different things may happen:

  • If the argument is of primitive type (int, char, double...), a new local variable will be created inside the function. That local variable is a copy of the original argument that we supplied. Whatever changes we do to that variable they will affect only that local variable. The original value will not be affected.
  • If the argument is of complex type (array, pointer...), a pointer to the original variable will be passed. So any changes we do in the function will affect the original variable. We will talk about pointers in the next lesson.

Control flow

What actually happens when we call a function? Let's explain this, using the example with findMax() above.

As you know the program starts from main() and it starts to execute line by line. We say that the program control is in main(). The execution reaches a function call. Next, the program will pause the execution of the current function and start to execute the called function line by line. So in our example, main() will be paused and the control will be transferred to scanf(). It will read the numbers that we input in the console and then return control to main().

main() will resume its execution and continue with the next line - the call to the max(). At this moment the control is transferred to max() it will execute and then return will return the control back to main().

When the main function returns, our program finishes and it returns control to the point of execution. Since this is a console application, the control will be returned to the console and the user can start another application.

Good practices

Functions in C(and in any other language) make the code more simple and reusable. For this reason, a function should be focused on doing
one thing. Avoid big, complex routines that do several tasks. It will be better to separate that in several functions.

This has huge advantages. A small function:

  • is easier to implement - less likely to contain a bug
  • is easier to understand - less time to debug
  • can be reused (called from different places in our program) without doing additional and unnecessary stuff

Naming should follow the variable naming practices that we discussed before.

  • A function name should be reasonably descriptive - If I have never seen that function and I see its prototype, I should have a good idea of what that function is all about
  • Use camilCase or underscore_in_names

Previous: C array

Next: Pointers

   Search this site: