Functions and Return Values in C

We learn in this lesson how return values work in C language. In particular, we see how to return a value from a function and how to use the return statement.

Return Values

We have said that, in computer science, we distinguish between procedures and functions.

A procedure can be seen as a macro-instruction, that is, an aggregate of simpler instructions that are executed sequentially.

Conversely, a function is an entity that takes parameters as input, performs operations on them, and returns a value. We can see a function as a black box that takes data as input and returns a result.

The C language does not make, as other languages do, a clear distinction between procedures and functions. In C, a procedure is simply a function that does not return any value, that is, it is a function of type void. Conversely, a function is a function that returns a value.

Definition

Procedures in C Language

A Procedure in C language is a function with return type void:

void procedure_name(parameter_list) {
    // procedure body
}

A procedure is also known as a void function in C language jargon.

Definition

Functions in C Language

A Function in C language is a function with return type different from void:

return_type function_name(parameter_list) {
    // function body
    return return_value;
}

In previous lessons we have already seen how to define functions that return a result. Now we study the details of this operation using the return statement.

The return Statement

Any non-void function, that is, one that returns a result, must necessarily contain a return statement that returns the return value.

The syntax of the return statement is as follows:

return expression;

The expression of the return statement can be a constant, a variable, or a more complex expression.

For example, it is very common to find return expressions written like this:

return 0;
return x;
return x + y;

Even more complex expressions can be used. It is not uncommon, for example, to have conditional expressions inside a return statement:

return (x > y) ? x : y;

In this example, the expression (x > y) is evaluated. If the result is true, the value of x is returned, otherwise the value of y is returned.

An important point of the return statement is that, once executed, the control of the function passes to the caller. In other words, the return statement terminates the execution of the function and returns control to the caller.

Since a return statement can appear anywhere in the function body, it is possible for a function to contain more than one return statement. In this case, only one of the return statements will be executed.

Let's take an example to clarify this concept:

int maximum(int x, int y) {
    if (x > y) {
        return x;
    } else {
        return y;
    }
}

In this example, the maximum function returns the maximum between two integers x and y. If x is greater than y, x is returned, otherwise y is returned. So the function has two return statements, but only one of them will be executed.

We could have written the above function also in this way:

int maximum(int x, int y) {
    if (x > y) {
        return x;
    }

    return y;
}

The maximum function realized in this way still works because, if x is greater than y, x is returned and the function terminates. That is, the function does not continue its execution after the return x; statement, so the return y; statement will never be executed.

Definition

return Statement

The return statement is used to return a value from a function. The return statement is followed by an expression that represents the return value:

return expression;

When a return statement is executed, the control of the function passes to the caller.

A function with return type different from void must necessarily have a return statement inside it. Otherwise, the control of the function reaches the end of it and the return value will be random. The problem could become catastrophic when you try to use the return value of a function that has not returned any value. In that case, the behavior of the program becomes unpredictable.

For example:

int sum(int x, int y) {
    int result = x + y;
    /* ERROR: return statement missing */
}

In this example, the sum function calculates the sum between two integers x and y, but does not return any value. If we try to store its result in a variable, the result will be random and the behavior of the program will be unpredictable:

int result = sum(3, 4);
/* From here on the behavior of the program is unpredictable */

Many compilers, including the GCC compiler, warn of the error of missing return in a non-void function. For example, the GCC compiler returns the following error:

error: control reaches end of non-void function [-Werror=return-type]

This error, in fact, is much more common than it seems. For example, when a function contains a series of if-else statements and not all possible cases have been considered, it could happen that none of the return statements are executed. In that case, the compiler warns of the error.

For example:

int maximum(int x, int y) {
    if (x > y) {
        return x;
    } else if (y > x) {
        return y;
    }
    /* ERROR: return statement missing */
}

In this example it seems at first glance that we have considered all possible cases, but in fact this is not the case. If x and y are equal, none of the two return statements will be executed. In that case, the compiler warns of the error.

Note

Missing return in a Non-void Function

A function with return type different from void must necessarily contain a return statement. Otherwise, the control of the function reaches the end of it and the return value will be random. The behavior of the program becomes unpredictable.

You must always check that all possible cases have been considered and that all possible cases have an associated return statement.

In any case, modern compilers warn of the error of missing return in a non-void function.

Type of Return Value

In principle, the expression of the return statement must be of the same type as the return type expected by the function.

For example:

float function() {
    return 3.14f;
}

In this case, the expression 3.14f is of type float, which is the same return type of the function function.

If this does not happen, two cases can occur:

  1. The compiler attempts an implicit conversion if possible.

    For example, if the function has int as return type but the expression of the return statement is of type float, the compiler performs an implicit conversion from float to int.

    int function() {
        return 3.14f;
    }
    

    This is possible because the compiler is able to convert a float value to an int value. Obviously this conversion is reported as a warning by the compiler since converting a float value to an int value involves the loss of the decimal part.

    The GCC compiler, for example, returns the following warning:

    warning: implicit conversion from 'float' to 'int'
    

    Conversely, if the conversion does not involve loss of information, the compiler does not return any warning.

  2. If implicit conversion is not possible, the compiler returns an error.

    Let's take the following example:

    int function() {
        return "hello";
    }
    

    In this case, the expression of the return statement is a string, that is, an array of characters. The return type of the function function is int, which is a data type different from char[]. In this case, the compiler returns an error:

    error: return makes integer from pointer without a cast
    

In summary:

Definition

Type of Return Value

The expression of the return statement must be of the same type as the return type expected by the function. Otherwise:

  1. The compiler attempts an implicit conversion if possible.
  2. If implicit conversion is not possible, the compiler returns an error.

return and void Functions

We have said that a void function does not return any value. Therefore, in this case, a return statement is not necessary.

However, it is possible to use a return statement in a void function. In that case, the return statement terminates the execution of the function and returns control to the caller. When used for these cases, the return statement must not be followed by any expression.

The syntax becomes:

return;

The usefulness of using a return in a void function is that, in case of errors or particular conditions, you can terminate the execution of the function and return control to the caller.

For example, suppose we have a void function that expects a positive integer as input. If the input number is negative, we can terminate the execution of the function and return control to the caller:

void function(int x) {
    if (x < 0) {
        printf("Error: the number must be positive\n");
        return;
    }

    // function body
}

In this example, if the number x is less than zero, an error message is printed and the execution of the function terminates.

Definition

return in void Functions

A void function can contain a return statement inside it. In that case, the return statement terminates the execution of the function and returns control to the caller.

Used in this context, the return statement must not be followed by any expression:

return;

In Summary

We have explored, in this lesson, the use of the return statement in a function:

  • The return statement is used to return a value from a function;
  • A function with return type different from void must necessarily contain at least one return statement;
  • The expression of the return statement must be of the same type as the return type expected by the function;
  • If implicit conversion is not possible, the compiler returns an error;
  • A void function does not return any value, but can contain a return statement to terminate the execution of the function itself early.