Why does flowing off the end of a non-void function without returning a value not produce a compiler error?
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.
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 areturn
statement, the effect is that of executingreturn 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.
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.