Skip to content
VLSI Mentor

C Part I · Module 5 · Functions and Program Decomposition

Return Values

Chapter 5.3 established that a result gets out of a function by being returned, and left return itself unexamined. It has appeared in every example so far — at the end of a function and in the middle of one, in functions that produce a value and in one that produces none, and in main, where the value goes somewhere Chapter 1.7 described and this module has not revisited. This chapter makes all of that precise. Most of it is straightforward, and two parts are not: what happens when a function that promises a value reaches the end of its body without producing one, and what to do when the answer you need has more than one part, since a C function can hand back exactly one value and no more.

Foundation17 min readCPart IFundamentalsFunctions

Part I · Module 5 · Chapter 5.4 · Return Values

1. What return Does

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
return expression;

Two things, both immediate:

The function ends, wherever the return appears, and the call expression takes on the value of that expression.

"The function ends" is literal. Statements after a return in that function do not run, loops in progress stop, and control goes back to just after the call — Chapter 5.2's step 5.

In a void function there is nothing to hand back, so the form is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
return;

which ends the function and nothing more. A void function may also simply reach its closing brace, which has the same effect.

2. The Return Type Is a Conversion Target

The return type is not a label on the function. It is the type the returned value is converted to, by exactly the rules Chapter 3.12 established for assignment.

Azvya Education Pvt. Ltd.VLSI Mentor
returning a value of the wrong type
#include <stdio.h>

int truncating(void)
{
    return 3.7;
}

int main(void)
{
    printf("%d\n", truncating());
    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

The compiler notices:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: implicit conversion from 'double' to 'int' changes value from 3.7
    to 3 [-Wliteral-conversion]

3.7 has type double. The function promises int. The value is converted on the way out, and converting a double to an int discards the fractional part — Chapter 3.12's rule, applied at the boundary rather than at an assignment.

This matters more than the toy example suggests. A function declared to return int that computes an average by dividing two double values will hand back a truncated result, and the only sign is a warning you may not have enabled. Choose the return type for the value you intend to produce, not for the one that happens to be convenient.

3. Returning Early

A return does not have to be the last statement. Returning as soon as the answer is known is normal and usually clearer:

Azvya Education Pvt. Ltd.VLSI Mentor
returning from inside a loop
#include <stdio.h>

int first_divisor(unsigned int value)
{
    for (unsigned int d = 2u; d < value; d++) {
        if (value % d == 0u) {
            printf("  found %u, returning immediately\n", d);
            return (int)d;
        }
    }

    printf("  loop finished without a divisor\n");
    return 0;
}

int main(void)
{
    printf("91: %d\n", first_divisor(91u));
    printf("97: %d\n", first_divisor(97u));

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  found 7, returning immediately
91: 7
  loop finished without a divisor
97: 0

Compare this with Chapter 4.6's version of the same search. There, stopping early needed a break, and the answer had to be carried out of the loop in a variable that was declared before it and inspected after it. Here the return is the exit and is the answer, and the second return 0 at the bottom handles the case where the loop completed.

4. Falling Off the End

What happens if a function promising an int reaches its closing brace without executing a return?

Azvya Education Pvt. Ltd.VLSI Mentor
one path returns, one does not
int maybe(int flag)
{
    if (flag) {
        return 1;
    }
}

The compiler warns:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: non-void function does not return a value in all control paths
    [-Wreturn-type]

A warning, not an error — the program builds. What C says about it has two halves, and most explanations give only one:

Reaching the closing brace of a non-void function is not, by itself, undefined. It becomes undefined behaviour only if the caller uses the value of that call.

So maybe(0); on a line by itself, ignoring the result, is defined. printf("%d\n", maybe(0)); is undefined — the standard imposes no requirements on what the program does, and there is no value to reason about. This example is deliberately not run anywhere in this chapter, because printing whatever appeared and calling it the answer would teach you something C does not promise.

The distinction is worth knowing, but it is not a licence. The practical rule is simpler:

Make sure every path through a non-void function executes a return, and compile with -Wreturn-type enabled so the compiler checks for you.

Usually that means a final return at the bottom, as in §3.

5. main Is a Genuine Exception

Azvya Education Pvt. Ltd.VLSI Mentor
main with no return statement
#include <stdio.h>

int main(void)
{
    printf("main reaches its closing brace without a return\n");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
main reaches its closing brace without a return
exit status: 0

No warning, and the exit status is 0. This is not luck:

Since C99, if main reaches its closing brace, the effect is the same as return 0;.

main is the only function with this rule. Chapter 1.7 established what that value is for — it is handed back to whatever started the program, and 0 conventionally means success.

Write return 0; anyway. It costs one line, it states the intent, and it keeps main looking like every other function in the file rather than a special case a reader has to remember. Every example in this curriculum does.

(You may also see return EXIT_SUCCESS;, which comes from a standard header and is more portable in principle than the literal 0. Chapter 12.7 covers program exit and return codes, including where that name comes from.)

6. One Value Only

A C function returns exactly one value. Not two, not none-and-also-a-flag. That constraint shapes a lot of C code, and it has four standard answers.

Two of them you can write today.

Return the result itself, when there is one result and it cannot fail. code_to_millivolts in Chapter 5.2 is this shape, and it is the one to prefer whenever it applies.

Return a status code, when what the caller actually needs to know is what happened:

Azvya Education Pvt. Ltd.VLSI Mentor
a function whose result is the verdict
#include <stdio.h>

const int result_ok = 0;
const int result_out_of_range = 1;
const int result_too_noisy = 2;

int check_sample(unsigned int code, unsigned int noise)
{
    if (code > 4095u) {
        return result_out_of_range;
    }

    if (noise > 50u) {
        return result_too_noisy;
    }

    return result_ok;
}

void report(unsigned int code, unsigned int noise)
{
    int result = check_sample(code, noise);

    if (result == result_ok) {
        printf("code %u accepted\n", code);
    } else if (result == result_out_of_range) {
        printf("code %u rejected: out of range\n", code);
    } else if (result == result_too_noisy) {
        printf("code %u rejected: noise %u too high\n", code, noise);
    } else {
        printf("code %u rejected: unrecognised result %d\n", code, result);
    }
}

int main(void)
{
    report(2048u, 10u);
    report(5000u, 10u);
    report(2048u, 90u);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
code 2048 accepted
code 5000 rejected: out of range
code 2048 rejected: noise 90 too high

Three decisions in that program are worth naming.

check_sample decides and report prints. Keeping the decision separate from the reporting is what makes check_sample checkable at inputs the program never produces — Chapter 5.1 §4's argument, applied.

The codes are named, not written as bare 1 and 2 at the call site. result == result_too_noisy says what it means; result == 2 does not.

And report uses an else if chain rather than a switch, which looks like the wrong choice for dispatching on a small integer. It is forced. Chapter 4.3 established that a case label must be an integer constant expression, and Chapter 2.11 established that const means this object must not be modified rather than the compiler knows this value. A const int object does not qualify:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: expression is not an integer constant expression; folding it to a
    constant is a GNU extension [-Werror,-Wgnu-folding-constant]

C does have two ways to name an integer constant that a switch will accept — enumeration constants (Chapter 10.8) and macros (Chapter 11.6) — and neither is available yet. Until then, naming the codes with const int and comparing them in a chain is the portable choice.

7. The Four Ways a Result Leaves a Function

ApproachWhen it fitsAvailable
Return the valueone result, cannot failnow
Return a status codethe verdict is the resultnow
Out-parametera result and a status, both neededChapter 7.5
Return a composite valueseveral results that belong togetherChapter 10.4

The third is the one C programs reach for constantly: the function returns a status code, and the caller supplies something the function can write the actual result into. That requires a value that identifies an object, which is Module 7's subject, and Chapter 5.3 explained precisely why it cannot be done with what you have — a parameter is a copy, and assigning to it changes nothing the caller can see.

The fourth bundles several values into one so that the single-value rule stops being a constraint. Chapter 10.4 covers it, including the fact that such a value is copied on the way out, exactly like every other return value.

Until Module 7, design functions so that the answer is one value. That is not a limitation to work around — it is a useful discipline, and a function with one clear result is usually a better function than one that reports three things at once.

8. What You Should Be Able to Say Now

  1. return expression; ends the function immediately and gives the call expression that value.
  2. return; ends a void function; reaching its closing brace does the same.
  3. The return type is a conversion target — return 3.7; from an int function yields 3.
  4. Returning early is normal, and it is often clearer than carrying an answer out in a variable.
  5. return leaves the function, not one loop — all enclosing loops end with it.
  6. Reaching the end of a non-void function is not itself undefined; it becomes undefined if the caller uses the value.
  7. -Wreturn-type catches it; make every path return.
  8. main is the exception: since C99, reaching its closing brace is the same as return 0. Write it anyway.
  9. A function returns exactly one value.
  10. The four ways out are the value, a status code, an out-parameter (7.5) and a composite value (10.4).

9. Exercises

Exercise 1 — predict the output. Do not compile yet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#include <stdio.h>

int clamp_to_ten(int n)
{
    if (n > 10) {
        return 10;
    }
    return n;
}

int main(void)
{
    printf("%d %d %d\n", clamp_to_ten(3), clamp_to_ten(10), clamp_to_ten(42));
    return 0;
}

Then say what Chapter 5.2 §5 tells you about the order those three calls happen in, and why it does not affect the printed line.

Exercise 2 — predict the conversion. Give the exact value each returns, and name the rule.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int a(void) { return 9 / 2; }
int b(void) { return 9.0 / 2; }
unsigned int c(void) { return 3; }
double d(void) { return 9 / 2; }

The last one is the interesting one — explain why it does not produce 4.5.

Exercise 3 — trace the early return. For §3's first_divisor called with 15, give every line printed and the returned value. Then say how many times the loop body ran.

Exercise 4 — classify the behaviour. For each, say whether it is defined, undefined, or a compile error, and why.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int f(int flag) { if (flag) { return 1; } }
...
f(0);                        /* (a) */
int x = f(0);                /* (b) */
int y = f(1);                /* (c) */
void g(void) { return; }     /* (d) */
void h(void) { return 5; }   /* (e) */

Exercise 5 — find the bug. This is meant to report whether any fault bit is set. Say what it actually does for a status of 0, which warning would reveal it, and give the smallest repair.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int has_fault(unsigned int status)
{
    if (status != 0u) {
        return 1;
    }
}

Exercise 6 — convert break to return. Chapter 4.6 §4's nested search used a flag to stop both loops. Rewrite it as a function int find_factor_pair(int target) that returns the first a for which a matching b exists, or 0 if none does. Say what replaced the flag and why no goto is needed.

Exercise 7 — choose the shape. For each, say which of §7's four approaches you would use and why. Where the answer is not yet available, name the chapter that provides it.

  • convert a code to millivolts
  • determine whether a frame header is valid
  • parse a value out of a field and say whether parsing succeeded
  • compute the minimum, maximum and average of a set of samples

Exercise 8 — defend the explicit return. main does not need return 0;. Give two reasons to write it anyway, one about readers and one about consistency, then say which standard made the omission safe.

10. Summary

  • return ends the function immediately and supplies the call expression's value.
  • The return type converts the returned value, by Chapter 3.12's rules.
  • Early returns are good style, and often replace a flag plus a break.
  • return leaves the whole function, including every enclosing loop — which is also the clean answer to Chapter 4.8's nested-exit problem.
  • Reaching the end of a non-void function is undefined only if the caller uses the value; -Wreturn-type catches the shape.
  • main is the one exception — since C99 its closing brace means return 0. Write it explicitly regardless.
  • A function returns one value: the result, a status code, an out-parameter (7.5), or a composite (10.4).
  • Naming status codes with const int works, but a switch will not accept them — that needs 10.8's or 11.6's constants.

Every function so far has been defined above its first call, because that was the only arrangement the compiler would accept. That constraint has been quietly shaping the files: main has had to sit at the bottom, below helpers the reader meets before knowing why they exist, and two functions that need to call each other cannot be arranged at all. The next chapter removes the constraint, and in doing so explains what the compiler is actually checking at every call site — and what it is reduced to doing when you do not tell it.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.