187

Ever since I realized many years ago, that this doesn't produce an error by default (in GCC at least), I've always wondered why?

I understand that you can issue compiler flags to produce a warning, but shouldn't it always be an error? Why does it make sense for a non-void function not returning a value to be valid?

An example as requested in the comments:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

...compiles.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Catskul
  • 17,916
  • 15
  • 84
  • 113
  • 9
    Alternatively, I treat all warnings however trivial like errors, and I activate all the warnings I can (with local deactivation if necessary... but then it's clear in the code why). – Matthieu M. Oct 23 '09 at 10:33
  • 10
    `-Werror=return-type` will treat just that warning as an error. I just ignored the warning and the couple of minutes of frustration tracking down an invalid `this` pointer lead me here and to this conclusion. – jozxyqk Nov 04 '13 at 07:46
  • This is made worse by the fact that flowing off the end of an `std::optional` function without returning returns a "true" optional – Rufus May 08 '20 at 01:28
  • @Rufus It doesn't have to. That was just what happened to happen on your machine / compiler / OS / lunar cycle. Whatever junk code the compiler generated because of the undefined behaviour just happened to just about look like a 'true' optional, whatever that is. – underscore_d Jun 15 '20 at 16:18
  • С++2003, 6.6.3 2: "Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function" – Konstantin Burlachenko Oct 27 '21 at 20:37

11 Answers11

159

C99 and C++ standards require non-void functions to return a value, except main. The missing return statement in main will be defined (to return 0). In C++ it's undefined behaviour if execution actually reaches the end of a non-void function other than main, while in C it's only UB if the caller uses the return value.

This means functions can look like they might reach the end without returning a value, but actually can't reach the closing }. John Kugelman's answer shows some examples, like a noreturn function called from one side of an if. It's only undefined behaviour if execution actually does get to the end without reaching a return earlier. The rationale includes that checking if every real code path returns a value is quite difficult (without knowing which functions never return), so it's not illegal to compile a function like your example, only to actually call it like your main does.

As an extension, at least one compiler (MSVC) allows a return value to be set with inline assembly, but most others still require a return statement in functions that use inline asm.

From C++11 draft:

§ 6.6.3/2

Flowing off the end of a function [...] results in undefined behavior in a value-returning function.

§ 3.6.1/5

If control reaches the end of main without encountering a return statement, the effect is that of executing

return 0;

Note that the behaviour described in C++ 6.6.3/2 is not the same in C.


gcc will give you a warning if you call it with -Wreturn-type option.

-Wreturn-type Warn whenever a function is defined with a return-type that defaults to int. Also warn about any return statement with no return-value in a function whose return-type is not void (falling off the end of the function body is considered returning without a value), and about a return statement with an expression in a function whose return-type is void.

This warning is enabled by -Wall.


Just as a curiosity, look what this code does:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

This code has formally undefined behaviour, and in practice it's calling convention and architecture dependent. On one particular system, with one particular compiler, the return value is the result of last expression evaluation, stored in the eax register of that system's processor, if you disable optimization.

This seems to be a consequence of GCC internals with optimization disabled, because in that case it picks the return-value register if it needs any to implement a statement. With optimization enabled in C++ mode, GCC and clang assume this path of execution is unreachable because it contains undefined behaviour. They don't even emit a ret instruction, so execution falls into the next function in the .text section. Of course undefined behaviour means that anything could happen.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Fernando N.
  • 6,369
  • 4
  • 27
  • 30
  • I'm picking this one because I feel like it's the most concise. – Catskul Oct 23 '09 at 18:04
  • 17
    I'd be wary of calling undefined behaviour "allowed", though admittedly I'd also be wrong to call it "prohibited". Not being an error and not requiring a diagnostic are not quite the same as "allowed". At the very least, your answer reads a bit like you're saying it's OK to do, which largely it is not. – Lightness Races in Orbit Jul 15 '11 at 14:26
  • @LightnessRacesinOrbit I hope it is less misleading now. – Fernando N. Mar 12 '12 at 14:06
  • 1
    I think it's better, though still tends towards suggesting to language newcomers that you will end up with a program whose semantics are defined if you omit a `return` statement in a function that is supposed to return a value. True, C++ "doesn't require" the `return` statement in that it "doesn't require" erroring about its omission, but C++ _does_ require it for your program to have well-defined semantics... which is what we strive for. – Lightness Races in Orbit Mar 12 '12 at 14:16
  • I guess require is a tricky word... some people like to ski off-piste – Fernando N. Mar 15 '12 at 13:04
  • 4
    @Catskul, why do you buy that argument? Wouldn't it be feasible, if not trivially easy, to identify exit points of a function and make sure they all return a value (and a value of the declared return type)? – BlueBomber Mar 11 '13 at 05:38
  • @BlueBomber I think you can easily cause the number of code paths in a function to explode such that it would be prohibitive to traverse them all. – Catskul Mar 26 '13 at 21:01
  • 3
    @Catskul, yes and no. Statically typed and/or compiled languages do lots of things that you would probably consider "prohibitively expensive", but because they only do it once, at compile time, they have negligible expense. Even having said that, I don't see why identifying exit points of a function needs to be super-linear: You just traverse the AST of the function and look for return or exit calls. That's linear time, which is decidedly efficient. – BlueBomber Mar 28 '13 at 18:43
  • 3
    @LightnessRacesinOrbit: If a function with a return value sometimes returns immediately with a value and sometimes calls another function which always exits via `throw` or `longjmp`, should the compiler require an unreachable `return` following the call to the non-returning function? The case where it isn't needed aren't very common, and a requirement to include it even in such cases probably wouldn't have been onerous, but the decision not to require it is reasonable. – supercat Sep 05 '13 at 23:23
  • 2
    @supercat: A super-intelligent compiler will not warn or error in such a case, but -- again -- this is essentially incalculable for the general case, so you're stuck with a general rule of thumb. If you know, though, that your end-of-function will never be reached, then you are so far from the semantics of traditional function handling that, yes, you can go ahead and do this and know that it's safe. Frankly, you're a layer below C++ at that point and, as such, all its guarantees are moot anyway. – Lightness Races in Orbit Sep 06 '13 at 23:03
  • @LightnessRacesinOrbit: I think there must be return statement exist if return type is not void. What is the reason that C (and Hence C++) left it undefined? any idea??? Thanks – Destructor Mar 21 '15 at 08:58
  • @meet: http://stackoverflow.com/a/9936084/560648 http://stackoverflow.com/a/1610454/560648 – Lightness Races in Orbit Mar 21 '15 at 16:31
  • @LightnessRacesinOrbit Well, it depends in which context the behavior is undefined. For instance, uninitialized dynamically allocated memory may hold unpredictable value. But it is allowed, of course. In this case I agree with you, but I also buy the path checking argument. –  May 13 '16 at 18:09
  • @user12918723509187: UB is UB. – Lightness Races in Orbit May 13 '16 at 22:47
  • @LightnessRacesinOrbit: By definition, yes. But it isn't the philosophy of C++ to be the guardian of everything that could possibly go wrong. As long as it is documented, it is the programmer's job to take care of not falling into the trap. –  May 13 '16 at 22:58
  • @user12918723509187: Exactly. – Lightness Races in Orbit May 13 '16 at 22:59
  • 1
    If a function `int foo(int x)`, among its other duties, returns the lowest prime factor of `x` if `x` is composite, and falls through if it's prime, and if the function is called by some functions which pass composite numbers and use the return value, and others that pass prime numbers but don't use the return value, behavior will be defined in all cases. Having the function return a particular value in the prime-number case may necessitate including an otherwise-unnecessary "load" instruction. – supercat Jun 23 '16 at 20:00
  • @BlueBomber: Yes, it would be easy for compilers (and thus the language) to require that every possible path out of a non-void function returned a value. The reason not to require that is that is may never be reached, e.g. in a function with a `while(1){}` loop and/or some `if() return x;` statements. But proving that it's never reached could potentially require solving the halting problem, so instead in C++ you're allowed to write code that has a `}` without an obvious `return x;` before it, but it's UB if that's reached. I made a major edit to this previously misleading answer. – Peter Cordes Jul 28 '22 at 04:37
  • @BlueBomber: [John Kugelman's answer](https://stackoverflow.com/a/1610111/224132) on this question shows much better examples of functions that don't provably return a value, but which actually always do. Java does what you suggest, sometimes requiring a useless `return 0` statement. – Peter Cordes Jul 28 '22 at 04:39
44

gcc does not by default check that all code paths return a value because in general this cannot be done. It assumes you know what you are doing. Consider a common example using enumerations:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

You the programmer know that, barring a bug, this method always returns a color. gcc trusts that you know what you are doing so it doesn't force you to put a return at the bottom of the function.

javac, on the other hand, tries to verify that all code paths return a value and throws an error if it cannot prove that they all do. This error is mandated by the Java language specification. Note that sometimes it is wrong and you have to put in an unnecessary return statement.

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

It's a philosophical difference. C and C++ are more permissive and trusting languages than Java or C# and so some errors in the newer languages are warnings in C/C++ and some warnings are ignored or off by default.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 2
    If javac actually checks code-paths wouldn't it see that you could never reach that point? – Chris Lutz Oct 22 '09 at 21:47
  • 3
    In the first one it doesn't give you credit for covering all of the enum cases (you need a default case or a return after the switch), and in the second one it doesn't know that `System.exit()` never returns. – John Kugelman Oct 22 '09 at 22:14
  • 2
    It seems straightforward for javac (an otherwise powerful compiler) to know that `System.exit()` never returns. I looked it up (http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html#exit%28int%29), and the docs just say it "never normally returns". I wonder what that means... – Paul Biggar Oct 23 '09 at 10:11
  • @Paul: it means they didn't have a goodeditor. All other languages say "never returns normally" -- i.e., "doesn't return using the normal return mechanism." – Max Lybbert Oct 23 '09 at 20:07
  • @JohnKugelman, I agree with your assessment of the first one, but I think the "problem" with the second example is more subtle: If the last statement in a sequence is an if/else (like it is here), all that is required for safety is to make sure the branches both return the same (and correct) type. If one branch returns the return value of a function call (as is the case for the if-branch here), that function doesn't have to be guaranteed to terminate for the program to pass compilation. – BlueBomber Mar 11 '13 at 05:44
  • 1
    I'd definitely prefer a compiler that at least warned if it encountered that first example, because the correctness of the logic would break if anyone added a new value to the enum. I'd want a default case that complained loudly and/or crashed (probably using an assertion). – Injektilo Aug 13 '15 at 14:11
  • And then had a way to shut the compiler up if you knew what you were doing. http://stackoverflow.com/q/3378560/5391501 for GCC and http://stackoverflow.com/q/7159348/5391501 for MSVC have the way to do this for those compilers. – Blair Houghton Mar 14 '16 at 18:22
  • The "philosophical difference" is between memory-safe languages like Java, and languages that have undefined behaviour like C++. Languages designed to be sandboxable mustn't allow nasal demons. (Or corrupting things that would require callers to spend extra code checking for.) – Peter Cordes Jul 28 '22 at 04:47
16

You mean, why flowing off the end of a value-returning function (i.e. exiting without an explicit return) is not an error?

Firstly, in C whether a function returns something meaningful or not is only critical when the executing code actually uses the returned value. Maybe the language didn't want to force you to return anything when you know that you are not going to use it anyway most of the time.

Secondly, apparently the language specification did not want to force the compiler authors to detect and verify all possible control paths for the presence of an explicit return (although in many cases this is not that difficult to do). Also, some control paths might lead into to non-returning functions - the trait that is generally non known to the compiler. Such paths can become a source of annoying false positives.

Note also, that C and C++ differ in their definitions of the behavior in this case. In C++ just flowing off the end of a value returning function is always undefined behavior (regardless of whether the function's result is used by the calling code). In C this causes undefined behavior only if the calling code tries to use the returned value.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
7

C and C++ have different rules.


The language rule in C is that if the closing } of a function that returns a non-void value is reached and the caller attempts to use that value, the behavior is undefined. Just falling off the end of the function has well defined behavior as long as the caller doesn't use the value.

It would be possible to require all possible control paths to execute a return statement before leaving the function, but C traditionally has not required compilers to do that kind of code analysis. (Many compilers will do that analysis anyway and issue a warning if appropriate.)

The main reason for allowing falling off the end of a non-void function is historical. K&R C (the version described in the 1978 first edition of Kernighan and Ritchie's book, before the 1989 ANSI and 1990 ISO C standard) did not have the void keyword or type. And prior to the 1999 ISO C standard, C had the "implicit int" rule, meaning that you could declare or define a function without an explicit return type and it would return an int result.

In K&R C, if you wanted a function that didn't return a result, you would define it without an explicit return type and simply not return a value:

#include <stdio.h>

do_something() {
    printf("Not returning a value\n");
}

int main() {
    do_something();
    return 0;
}

The function would actually return some garbage int value which the caller would quietly ignore.

In modern C, you would write:

#include <stdio.h>

void do_something(void) {
    printf("Not returning a value\n");
}

int main(void) {
    do_something();
}

which guarantees that the caller can't try to use the returned value. As of C89/C90, the language still supported the old style to avoid breaking existing code. When the implicit int rule was dropped in C99, the requirements on non-void functions failing to return a value were not changed (and most C99 and later compilers still support the implicit int rule by default, probably with a warning, so old K&R C code can still be compiled).


In C++, flowing off the end of a function other than a constructor, a destructor, a void function, or main results in undefined behavior, regardless of what the caller tries to do with the result.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
5

It is legal under C/C++ to not return from a function that claims to return something. There are a number of use cases, such as calling exit(-1), or a function that calls it or throws an exception.

The compiler is not going to reject legal C++ even if it leads to UB if you are asking it not to. In particular, you are asking for no warnings to be generated. (Gcc still turns on some by default, but when added those seem to align with new features not new warnings for old features)

Changing the default no-arg gcc to emit some warnings could be a breaking change for existing scripts or make systems. Well designed ones either -Wall and deal with warnings, or toggle individual warnings.

Learning to use a C++ tool chain is a barrier to learning to be a C++ programmer, but C++ tool chains are typically written by and for experts.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Yeah, in my `Makefile` I have it running with `-Wall -Wpedantic -Werror`, but this was a one-off test script that I forgot to supply the arguments to. – Nic May 28 '16 at 20:31
  • 2
    As an example, making [`-Wduplicated-cond`](https://gcc.gnu.org/ml/gcc-patches/2015-10/msg00245.html) part of `-Wall` broke GCC bootstrap. Some warnings that seem appropriate in *most* code is not appropriate in all code. That's why they're not enabled by default. – uh oh somebody needs a pupper May 28 '16 at 20:31
  • your first sentence seems to be in contradiction with the quote in the accepted answer "Flowing off .... undefined behaviour...". Or is ub considered "legal"? Or do you mean that it is not UB unless the (not) returned value is actually used? I am worried about the C++ case btw – 463035818_is_not_an_ai Mar 22 '17 at 13:34
  • 1
    @tobi303 `int foo() { exit(-1); }` does not return `int` from a function that "claims to return int". This is legal in C++. Now, it doesn't return *anything*; the end of that function is never reached. *Actually reaching* the end of `foo` would be undefined behavior. Ignoring end of process cases, `int foo() { throw 3.14; }` also claims to return `int` but never does. – Yakk - Adam Nevraumont Mar 22 '17 at 13:39
  • 1
    so i guess `void* foo(void* arg) { pthread_exit(NULL); }` is fine for the same reason (when its only usage is via `pthread_create(...,...,foo,...);`) – 463035818_is_not_an_ai Mar 22 '17 at 13:51
2

I believe this is because of legacy code (C never required return statement so did C++). There is probably huge code base relying on that "feature". But at least there is -Werror=return-type flag on many compilers (including gcc and clang).

d.lozinski
  • 21
  • 1
  • What do you mean "so did C++"? This is something the two languages differ on. In C++ it's UB right away, in C only if you use the return value. – Peter Cordes Jul 28 '22 at 04:48
  • At which point do we enter UB territory? Immediately after the call to that function? – KcFnMi Feb 19 '23 at 09:37
  • Probably at the point where you fall off the end of a function returning std::string. Or some other non-trivial object. – Robin Davies Mar 12 '23 at 09:46
1

In some limited and rare cases, flowing off the end of a non-void function without returning a value could be useful. Like the following MSVC-specific code:

double pi()
{
    __asm fldpi
}

This function returns pi using x86 assembly. Unlike assembly in GCC, I know of no way to use return to do this without involving overhead in the result.

As far as I know, mainstream C++ compilers should emit at least warnings for apparently invalid code. If I make the body of pi() empty, GCC/Clang will report a warning, and MSVC will report an error.

People mentioned exceptions and exit in some answers. Those are not valid reasons. Either throwing an exception, or calling exit, will not make the function execution flow off the end. And the compilers know it: writing a throw statement or calling exit in the empty body of pi() will stop any warnings or errors from a compiler.

Yongwei Wu
  • 5,292
  • 37
  • 49
  • MSVC specifically supports falling off the end of a non-`void` function after inline asm leaves a value in the return-value register. (x87 `st0` in this case, EAX for integer. And maybe xmm0 in a calling convention that returns float/double in xmm0 instead of st0). Defining this behaviour is specific to MSVC; not even clang with `-fasm-blocks` to support the same syntax makes this safe. See [Does \_\_asm{}; return the value of eax?](//stackoverflow.com/q/36802683) – Peter Cordes Jan 15 '20 at 23:07
0

Under what circumstances doesn't it produce an error? If it declares a return type and doesn't return something, it sounds like an error to me.

The one exception I can think of is the main() function, which doesn't need a return statement at all (at least in C++; I don't have either of the C standards handy). If there is no return, it will act as if return 0; is the last statement.

David Thornley
  • 56,304
  • 9
  • 91
  • 158
0

I was getting that warning because i forgot to add the statement itr = itr ->currentNode; basically missing that statement, function get into infinte loop and was never returning a value, That's the reason i was getting that warning during the compilation time

void* list_get(list* lst, int idx){

    node* itr = lst->head;
    
    if (idx >= lst->size){
        printf("list out of index");
        exit(1);
    }
    
    while(itr != NULL){
        if(itr->index == idx){
           return  itr->element;
        }
        itr = itr->currentNode;
     
    }


}
Arpan Saini
  • 4,623
  • 1
  • 42
  • 50
-2

Sounds like you need to turn up your compiler warnings:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$
Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • 6
    Saying "turn on -Werror" is a non-answer. Clearly there is a difference in severity between issues classified as warnings and errors, and gcc treats this one as the less severe class. – Cascabel Oct 22 '09 at 21:30
  • @Jefromi - I said turn up warnings, and I'd say most of `-Wall` _should_ probably be errors anyway (especially this case). But the reason I had all three of those in there is because that's what I have `gcc` aliased to on my system. – Chris Lutz Oct 22 '09 at 21:33
  • 2
    @Jefromi: From the pure language point of view, there's no difference between warnings and errors. The compiler is only required to issue a "disgnostic message". There's no requirement to stop compilation or call something "an eror" and something else "a warning". One a diagnostic message is issued (or any kind), it is entirely up to you to make a decision. – AnT stands with Russia Oct 22 '09 at 21:38
  • @Chris - ah, okay. And I do tend to agree with you about the warnings really being errors. I myself compile with -Werror whenever I can - it's hard when you're not responsible for all the code, though. – Cascabel Oct 22 '09 at 21:39
  • 1
    Then again, the issue in question causes UB. Compilers are not required to catch UB at all. – AnT stands with Russia Oct 22 '09 at 21:39
  • @Andrey: I wasn't trying to speak from a pure language point of view. Compilers simply try to help you make that decision by choosing a single word based on how severe the issue probably is, and given that fact, we can ask the question of which word should be chosen for a particular issue, yes? – Cascabel Oct 22 '09 at 21:41
  • @Andrey - According to pmg's answer, it's a constraint violation, and must therefore issue a diagnostic. – Chris Lutz Oct 22 '09 at 22:02
  • I didn't downvote, but the others probably did because the original question contains the quote "I understand that you can issue compiler flags to produce a warning, but shouldn't it always be an error?" – Catskul Oct 22 '09 at 22:26
  • @Chris: I think people are expecting an answer of the form "gcc does what it does because ___" not "gcc can be made to do the right thing by ___". You've got my +1, though! – Cascabel Oct 22 '09 at 22:26
  • 1
    In 6.9.1/12 in n1256 it says "If the } that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined." – Johannes Schaub - litb Oct 22 '09 at 23:15
  • 2
    @Chris Lutz: I don't see it. It is a constraint violation to use an *explicit* empty `return;` in a non-void function, and it is a constraint violation to use `return ;` in a void function. But that's, I believe, not the topic. The OP, as I understood it, is about exiting a non-void function without a `return` (just allowing control to flow off the end of the function). It is not a constraint violation, AFAIK. The standard just says it is always UB in C++ and sometimes UB in C. – AnT stands with Russia Oct 22 '09 at 23:18
  • @AndreyT - Ah. Before the code example, it said "return from non-void function without returning a value" which I took to mean an explicitly empty return. – Chris Lutz Oct 23 '09 at 03:11
-4

It is a constraint violation in c99, but not in c89. Contrast:

c89:

3.6.6.4 The return statement

Constraints

A return statement with an expression shall not appear in a function whose return type is void .

c99:

6.8.6.4 The return statement

Constraints

A return statement with an expression shall not appear in a function whose return type is void. A return statement without an expression shall only appear in a function whose return type is void.

Even in --std=c99 mode, gcc will only throw a warning (although without needing to enable additional -W flags, as is required by default or in c89/90).

Edit to add that in c89, "reaching the } that terminates a function is equivalent to executing a return statement without an expression" (3.6.6.4). However, in c99 the behavior is undefined (6.9.1).

mbauman
  • 30,958
  • 4
  • 88
  • 123
  • 5
    Note that this only covers explicit `return` statements. It doesn't cover falling off the end of a function without returning a value. – John Kugelman Oct 22 '09 at 22:21
  • 4
    Note that C99 misses "reaching the } that terminates a function is equivalent to executing a return statement without an expression" so it's not made a constraint violation, and thus no diagnostic is required. – Johannes Schaub - litb Oct 22 '09 at 23:24