By Catskul


2009-10-22 21:21:24 8 Comments

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.

9 comments

@Yongwei Wu 2019-09-23 16:59:11

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.

@AnT 2009-10-22 21:27:37

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.

@Chris Lutz 2009-10-22 21:36:51

+1 but can't C++ omit return statements from the end of main()?

@AnT 2009-10-22 21:40:50

@Chris Lutz: Yes, main is special in that regard.

@Yakk - Adam Nevraumont 2016-05-28 20:28:11

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.

@Fund Monica's Lawsuit 2016-05-28 20:31:11

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.

@uh oh somebody needs a pupper 2016-05-28 20:31:39

As an example, making -Wduplicated-cond 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.

@formerlyknownas_463035818 2017-03-22 13:34:17

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

@Yakk - Adam Nevraumont 2017-03-22 13:39:21

@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.

@formerlyknownas_463035818 2017-03-22 13:51:04

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,...);)

@fnieto - Fernando Nieto 2009-10-22 22:51:22

C99 and C++ standards don't require functions to return a value. The missing return statement in a value-returning function will be defined (to return 0) only in the main function.

The rationale includes that checking if every code path returns a value is quite difficult, and a return value could be set with embedded assembler or other tricky methods.

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.

@Catskul 2009-10-23 18:04:20

I'm picking this one because I feel like it's the most concise.

@Lightness Races with Monica 2011-07-15 14:26:42

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.

@fnieto - Fernando Nieto 2012-03-12 14:06:53

@LightnessRacesinOrbit I hope it is less misleading now.

@Lightness Races with Monica 2012-03-12 14:16:46

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.

@fnieto - Fernando Nieto 2012-03-15 13:04:38

I guess require is a tricky word... some people like to ski off-piste

@BlueBomber 2013-03-11 05:38:59

@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)?

@Catskul 2013-03-26 21:01:11

@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.

@BlueBomber 2013-03-28 18:43:37

@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.

@supercat 2013-09-05 23:23:24

@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.

@Lightness Races with Monica 2013-09-06 23:03:45

@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.

@Destructor 2015-03-21 08:58:51

@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

@Lightness Races with Monica 2015-03-21 16:31:47

@user2321808 2016-05-13 18:09:55

@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.

@Lightness Races with Monica 2016-05-13 22:47:53

@user12918723509187: UB is UB.

@user2321808 2016-05-13 22:58:33

@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.

@Lightness Races with Monica 2016-05-13 22:59:00

@user12918723509187: Exactly.

@supercat 2016-06-23 20:00:15

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.

@d.lozinski 2015-04-16 18:11:18

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).

@John Kugelman supports Monica 2009-10-22 21:34:11

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.

@Chris Lutz 2009-10-22 21:47:05

If javac actually checks code-paths wouldn't it see that you could never reach that point?

@John Kugelman supports Monica 2009-10-22 22:14:58

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.

@Paul Biggar 2009-10-23 10:11:21

It seems straightforward for javac (an otherwise powerful compiler) to know that System.exit() never returns. I looked it up (java.sun.com/j2se/1.4.2/docs/api/java/lang/…), and the docs just say it "never normally returns". I wonder what that means...

@Max Lybbert 2009-10-23 20:07:21

@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."

@BlueBomber 2013-03-11 05:44:23

@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.

@Injektilo 2015-08-13 14:11:25

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).

@Blair Houghton 2016-03-14 18:22:07

And then had a way to shut the compiler up if you knew what you were doing. stackoverflow.com/q/3378560/5391501 for GCC and stackoverflow.com/q/7159348/5391501 for MSVC have the way to do this for those compilers.

@Matt B. 2009-10-22 21:41:12

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).

@John Kugelman supports Monica 2009-10-22 22:21:09

Note that this only covers explicit return statements. It doesn't cover falling off the end of a function without returning a value.

@Johannes Schaub - litb 2009-10-22 23:24:28

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.

@Chris Lutz 2009-10-22 21:28:11

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
$

@Cascabel 2009-10-22 21:30:59

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.

@Chris Lutz 2009-10-22 21:33:23

@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.

@AnT 2009-10-22 21:38:47

@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.

@Cascabel 2009-10-22 21:39:26

@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.

@AnT 2009-10-22 21:39:43

Then again, the issue in question causes UB. Compilers are not required to catch UB at all.

@Cascabel 2009-10-22 21:41:14

@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?

@Chris Lutz 2009-10-22 22:02:21

@Andrey - According to pmg's answer, it's a constraint violation, and must therefore issue a diagnostic.

@Catskul 2009-10-22 22:26:41

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?"

@Cascabel 2009-10-22 22:26:54

@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!

@Johannes Schaub - litb 2009-10-22 23:15:03

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."

@AnT 2009-10-22 23:18:26

@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 <value>; 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.

@Chris Lutz 2009-10-23 03:11:04

@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.

@David Thornley 2009-10-22 21:25:37

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.

@Chris Lutz 2009-10-22 21:30:33

main() needs a return in C.

@Bill 2009-10-22 21:36:37

@Jefromi: The OP is asking about a non-void function without a return <value>; statement

@Johannes Schaub - litb 2009-10-22 21:37:12

main automatically returns 0 in C and C++. C89 needs an explicit return.

@pmg 2009-10-22 21:56:49

@Chris: in C99 there's an implicit return 0; at the end of main() (and main() only). But it's good style to add return 0; anyway.

Related Questions

Sponsored Content

6 Answered Questions

2 Answered Questions

[SOLVED] "void value not ignored as it ought to be" on non-void function

  • 2018-07-15 20:35:06
  • Fernando Andreas Sahmkow Beico
  • 434 View
  • 2 Score
  • 2 Answer
  • Tags:   c++ gcc c++14

1 Answered Questions

3 Answered Questions

2 Answered Questions

4 Answered Questions

8 Answered Questions

[SOLVED] Why and how does GCC compile a function with a missing return statement?

  • 2011-09-02 08:22:51
  • harrison
  • 3150 View
  • 9 Score
  • 8 Answer
  • Tags:   c linux gcc

Sponsored Content