Why Does This C++ Snippet Compile (Non-Void Function Does Not Return a Value)

Why does this C++ snippet compile (non-void function does not return a value)

This is undefined behavior from the C++11 draft standard section 6.6.3 The return statement paragraph 2 which says:

[...] 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. [...]

This means that the compiler is not obligated provide an error nor a warning usually because it can be difficult to diagnose in all cases. We can see this from the definition of undefined behavior in the draft standard in section 1.3.24 which says:

[...]Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]

Although in this case we can get both gcc and clang to generate a wanring using the -Wall flag, which gives me a warning similar to this:

warning: control reaches end of non-void function [-Wreturn-type]

We can turn this particular warning into an error using the -Werror=return-type flag. I also like to use -Wextra -Wconversion -pedantic for my own personal projects.

As ComicSansMS mentions in Visual Studio this code would generate C4716 which is an error by default, the message I see is:

error C4716: 'Min' : must return a value

and in the case where not all code paths would return a value then it would generate C4715, which is a warning.

Why does flowing off the end of a non-void function without returning a value not produce a compiler error?

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.

Is it Undefined behavior to not having a return statement for a non-void function in which control can never off over the end?

The two statements are in no way contradictory.

The first statement is about what happens when control flow exits a non-void function without executing a return statement. The second statement is about what happens when control flow does not exit the function at all. Calls to functions like exit or std::terminate do not ever have control flow proceed past the point when those functions are called.

But that has nothing to do with the nature of the return value.

The behavior of the program when a non-void function runs out of stuff to do without an explicit return statement (or throw. Or co_return these days) is governed by [stmt.return]/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.

why does my gcc allow this empty function with pointer return type?

The C standard does not require a function declared with a non-void return type to return a value.

The standard says that if a function returns without returning a value (because execution flows to its closing }) and the value of the function is used, then the behavior is not defined. However, there are situations where it makes sense for a function to return a value in some cases and not others. For example, you can have a function that sets or gets a value depending upon a command parameter:

int AccessState(int Command, SomeStructure *S,...)
{
switch (Command)
{
case 0: // Get the state.
return S->Value;
case 1: // Set the state from the optional int parameter following S.
va_list ap;
va_start(ap, S);
S->Value = va_arg(S, int);
va_end(ap);
break;
}
}

Doing this is unusual (and may not be good design), so a compiler may warn about it. However, to conform to the C standard, a C implementation must not reject a non-void function in which control may reach the closing }.

Why is 'control reaches end of non-void function' only a warning? Is it legal?

As already stated by Bathsheba, flowing off the end of a non-void function is indeed undefined behavior. The reason why this is a warning, not an error, is that you might be able to proof that this will never happen. For example consider this:

// My magic function.
// It is forbidden to call it with values between -5 and 5
bool fun (int i) {
if (i >= 5)
return true;
if (i <= -5)
return false;
}

Now you have well defined behavior as long as the users of the function read the comment carefully. This is obviously a dangerous thing to do, so the compiler warns you not to do it.

Edit:
As MSalters mentioned, this is less dangerous for private member functions because the class invariant can guarantee that the function is never used incorrectly. Consider this example:

// Every member function of SomeClass makes sure 
// that i_ is in the legal range when it's done
class SomeClass {
public:
SomeClass () : i_{27} {}
// Possibly more public or private functions that all make sure i_ stays valid
private:
bool foo () {if (i_ > 3) return true;}
int i_;
};

Now you (as the class maintainer) can make sure that i_ is always at least 4 and foo will always work fine. Personally, I would still avoid that because it hurts maintainability and readability, but it is better than the free version because now "only" all class maintainers need to worry about this invariant/can make a mistake.

Why does it work when a function missing a return statement at the end?

If there is an Intel-compatible computer then it seems the function returns the result in register EAX.

At the same time the function uses this register for variable i.

So after the loop the register always contains 44.

This value receives the caller.

Of course the function has undefined behaviour. It shall have an explicit return statement with an expression at the end of the function after the loop.

Why any type can be assigned void* but I can not return void* for any type function?

void is an incomplete type; it does not specify an integer type, a floating-point type, a structure type, or any other specific type. It is largely a placeholder.

void * is a pointer to void. By pointing to void, we leave it open what a pointer points to. So a void * could be an address that was derived from an int, from structure, or from any other object type.

So void * is not an incomplete type. It is some sort of pointer type.

When a void * is assigned to an int *, the C standard says an implicit conversion is done, because a void * could be pointing to an int.

Note that void * and int * are significantly different types. void * could be pointing to any object type, but int * should point to an int. The C standard even allows a void * and an int * to have different sizes.

In turn, this means that pointing to a void * and pointing to an int * are different. So, when a void ** is assigned to an int *, this violates a constraint in the C standard. The void ** should be pointing to a void *. The void ** should not be pointing to an int *.

why does recursion function works in visual studio eventhough there is no return value

Your function has undefined behavior when it doesn't return anything. The number 1349009568 is meaningless and probably random. For me, it shows a 0, and has the same output as this version which will never return anything.

int test(int numb) {
if (false) return -1;
}

Why do programs in C compile even when the return statement is missing?

C is an old language and at the time it was introduced, returning integers was common enough to be the default return type of a function. People later started realizing that with more complicated return types, it was best to specify int to be sure you are not forgetting the return type, but in order to maintain backwards compatibility with old code, C could not remove this default behavior. Instead most compilers issue warnings.

If the function reaches the end without a return statement an undefined value is returned except in the main function, 0 is returned. This has the same reason as above.

/* implicit declaration of printf as: int printf(int); */

/* implicit int type */
main()
{
printf("hello, world\n");
} /* implicit return 0; */

C - Confused by 'control may reach end of non-void function' when using certain if/else syntax

The problem is that C compiler is not smart enough to figure out that there is no scenario when the first function would reach the end without returning a value:

  • When the loop executes at least once, there is a conditional return from both branches of the if statement, and
  • When the loop never executes, there is going to be a return from the conditional statement at the top of the function.

C standard does not require such checks, so the compiler issues a warning.



Related Topics



Leave a reply



Submit