UB when flowing off end of value-returning function

S

Scott Meyers

Both C++98/03 and draft C++0x say this:
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.

Does anybody know why this is undefined behavior instead of a hard error?

It has an interesting implication for lambda expressions. Lambdas declaring a
return type but returning nothing yield UB and, with the compilers I tested,
don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return something"; };

All enlightenment appreciated.

Scott
 
S

Scott Meyers

I think it is because such a function foo() can call another function bar()
which can throw an exception but this fact is not known to the compiler when
compiling foo().

But then foo will not flow off the end. It will exit with an exception.

Scott
 
S

Scott Meyers

int foo()
{
bar(); // compiler does not know that bar() throws
// to the compiler it looks like we are flowing off the end
// we are not however, what should compiler do?
}

I'd like to see an error. The only time this doesn't have UB is when the author
of foo knows that bar always throws, and if the author of foo knows that, that
author should not declare foo as having an int return type, because no int will
ever be returned.

Scott
 
S

Scott Meyers

int foo()
{
if (wibble())
return 42;
bar();
}

Same problem, from my perspective, but anyway, we're straying from the original
question. Does anybody know why the decision was made to say that flowing off
the end of a value-returning function is UB instead of an error?

Scott
 
B

Balog Pal

Scott Meyers said:
Both C++98/03 and draft C++0x say this:


Does anybody know why this is undefined behavior instead of a hard error?

What kind of error? Compile-time -- forcing bogus return near the end when
the programmer has no intention ever reach that point? Or runtime -- say
emitting call to terminate? Well, the latter could be done, and I doubt it
would add much noise, but I don't see much benefit.

The compilers I use (VC, gcc) emit warnings 'not all control paths return a
value', catching most real problems, and flag some false positives.
It has an interesting implication for lambda expressions. Lambdas
declaring a return type but returning nothing yield UB and, with the
compilers I tested, don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return
something"; };

That's an interesting observation. I'd expect a diagnostic. Do they declare
the new features as finished or yet experimental? I'd guess the latter and
in release form the warning will be there.
 
J

Juha Nieminen

Leigh Johnston said:
I think it is because such a function foo() can call another function
bar() which can throw an exception but this fact is not known to the
compiler when compiling foo().

When the compiler sees something like:

int foo()
{
bar();
}

then couldn't the compiler think like "hmm, there's no return statement
in this execution path even though there should be... but maybe the
programmer knows what he's doing and that execution path will never be
performed; I'll just make sure of that by changing the function to the
equivalent of:"

int foo()
{
bar();
assert(false);
}

"This way if it was a mistake, like it probably was, it will be
caught pretty fast."

I assume that since the standard says it's undefined behavior, the
compiler *is* allowed to do that.
 
A

Alf P. Steinbach /Usenet

* Scott Meyers, on 02.11.2010 18:41:
Same problem, from my perspective, but anyway, we're straying from the original
question. Does anybody know why the decision was made to say that flowing off
the end of a value-returning function is UB instead of an error?

If there was a rule that flowing off the end should be an error, the compiler
would have to pre-emptively flag /possible/ such as errors, in order to
guaranteed diagnose all actual such as errors.

Consider:

bool error( std::string const& s )
{
funcThatCallsExit( s );
}

Versus:

bool error( std::string const& s )
{
funcThatCallsExit( s );
return false; // Dummy to satisfy hypothetical error rule.
}

The bool result type is practically useful even though the actual result won't
ever be used (the function doesn't return).


Cheers & hth.,

- Alf
 
S

Scott Meyers

Scott Meyers said:
It has an interesting implication for lambda expressions. Lambdas declaring a
return type but returning nothing yield UB and, with the compilers I tested,
don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return something"; };

That's an interesting observation. I'd expect a diagnostic. Do they declare the
new features as finished or yet experimental? I'd guess the latter and in
release form the warning will be there.

VC10 with /W4 says nothing, and that's a release compiler. Gcc 4.5 with -Wall
issues a warning.

Scott
 
S

Scott Meyers

bool error( std::string const& s )
{
funcThatCallsExit( s );
return false; // Dummy to satisfy hypothetical error rule.
}

The bool result type is practically useful even though the actual result won't
ever be used (the function doesn't return).

How is the bool result useful?

Scott
 
A

Alf P. Steinbach /Usenet

* Scott Meyers, on 02.11.2010 19:57:
How is the bool result useful?

E.g you can write

someonesCStyleFunc( blah, bah, ditto )
Cheers,

- Alf
 
G

Gil

Both C++98/03 and draft C++0x say this:
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.

Does anybody know why this is undefined behavior instead of a hard error?

It has an interesting implication for lambda expressions.  Lambdas declaring a
return type but returning nothing yield UB and, with the compilers I tested,
don't necessarily issue a warning:

   auto f = []()->int { std::cout << "Oops, I forgot to return something"; };

All enlightenment appreciated.

Scott

--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near Seattle
(http://cppandbeyond.com/)
* License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
personal use (http://tinyurl.com/yl5ka5p).

I believe Java has a "not all code paths return a value" compile
error.
so probably Java users are asking questions like 'how the heck can I
tell compiler that foo() never returns?'

however, if you want that behavior - a nice compiler should offer such
options: gcc does but remember there is a cost in compilation speed.
my C++ compiler trusts me to enable/disable this. why would I want
this to be non configurable compile error by default?

also check the code below; do you think it would be better to generate
compiler errors by default?

/**
* @file: end_of_fun_ub.cpp
* @author: gil
*/

#include <iostream>

int bar( ) {
//speed paranoia: optimize a mov to eax away
int a = 41, b = ++a;
}

int foo( ) {
//speed paranoia: shortcut return
asm (
"xor %eax, %eax"
);
}

enum ABC { A, B };

template< ABC a = A > struct APolicy { };
template< > struct APolicy< A > {
static const ABC value = A;
};
template< > struct APolicy< B > {
static const ABC value = B;
};

template< typename P >
int abc( ABC a = P::value ) {
//compile time fixed set of values
switch ( a ) {
case A : return 1;
case B : return 2;
}
//throw "not all code paths CAN return a value";
}

int main( ) {
std::cout << "foo: " << foo( ) << std::endl;
std::cout << "bar: " << bar( ) << std::endl;
std::cout << "abc: " << abc< APolicy<> >( ) << std::endl;
}

/* end
*/

hth,
gil
 
M

Marcel Müller

Scott said:
Both C++98/03 and draft C++0x say this:


Does anybody know why this is undefined behavior instead of a hard error?

Beyond all pros and cons mentioned in the discussion the basic problem
is in my opinion the the language does not provide a way to declare a
function that must never return normally. The use cases would be
exception factories as well as some library functions like abort(). In
kernel programming it might be useful too. I missed that several times.
I have seen proprietary extensions for that purpose many years ago with
Watcom C++.

However, maybe the standard did not want to force the impact that every
compiler must do a full code path analysis.

It has an interesting implication for lambda expressions. Lambdas
declaring a return type but returning nothing yield UB and, with the
compilers I tested, don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return
something"; };

I guess, that this is simply a bug (from the compilers point of view).
The lambda implementations are quite new.


Marcel

Btw. The second feature I regularly miss is to declare a function as
side effect free, which implies that the return value is a compile time
constant if all arguments are compile time constant.
I have seen (and used) this on a very old compiler for the inmos
Transputer platform. Looking at the generated code this turned out to
give the compiler room for very advanced optimizations.
Of course, it makes no difference as long as the code is inlined. But
this is not always an option, e.g. because PIMPL.
 
J

Johannes Schaub (litb)

Scott said:
Both C++98/03 and draft C++0x say this:


Does anybody know why this is undefined behavior instead of a hard error?

Either you require the user to write unnecessary code in certain cases to
make it statically provable that a value is always returned or you require a
runtime error diagnostic. Both bloats the code and isn't in the spirit of
C++, I think.
 
J

Johannes Schaub (litb)

Scott said:
Scott Meyers said:
It has an interesting implication for lambda expressions. Lambdas
declaring a return type but returning nothing yield UB and, with the
compilers I tested, don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return something";
};

That's an interesting observation. I'd expect a diagnostic. Do they
declare the new features as finished or yet experimental? I'd guess the
latter and in release form the warning will be there.

VC10 with /W4 says nothing, and that's a release compiler. Gcc 4.5 with
-Wall issues a warning.

Maybe the compiler optimized the variable away (and if you're not calling
"f", there is no undefined behavior)? Have you tried calling "f" so it can't
optimize it away?
 
S

Scott Meyers

Compilers aren't very good at detecting whether a function falls off the end.

I can't help wondering whether this is a technology limitation, a blast from the
past, or just laziness on the part of compiler writers. We've been writing
compilers for some 50 years now, is it really the case that asking compiler
writers to track control flow arcs out of a function and make sure a value is
returned along all of them (excluding exception-based paths) really asking too
much? My guess is no, but I'm not a compiler writer.

What do other, more modern, languages do? As far as I know, there is no UB in
languages like Java and C#, so how do they deal with this issue? If they reject
such code during compilation (and I don't know if they do), that would suggest
that tracking control flow arcs is not an unreasonable burden to put on compiler
writers.

Scott
 
L

LR

Scott said:
I can't help wondering whether this is a technology limitation, a
blast from the past, or just laziness on the part of compiler
writers. We've been writing compilers for some 50 years now, is it
really the case that asking compiler writers to track control flow
arcs out of a function and make sure a value is returned along all of
them (excluding exception-based paths) really asking too much? My
guess is no, but I'm not a compiler writer.

I'm not a compiler writer either, but I think this is a difficult problem.

int f(const int x) {
if(x == 33) return 1;
if(x == 34) return 2;
if(x != 33 && x != 34) return 3;
}

I'd agree that this is not pretty, good or easy to test, but I think
it's simple. What should a compiler do with this code? And also,
although I hesitate to ask, what is it worth to the compiler writer and
the customers to have this case detected?

FWIW in VS2008 the code gives me a warning that not all control paths
return a value. http://www.comeaucomputing.com/tryitout/ gives a warning
about a missing return statement.



LR
 
S

Scott Meyers

I'm not a compiler writer either, but I think this is a difficult problem.

int f(const int x) {
if(x == 33) return 1;
if(x == 34) return 2;
if(x != 33&& x != 34) return 3;
}

I'd agree that this is not pretty, good or easy to test, but I think
it's simple. What should a compiler do with this code? And also,
although I hesitate to ask, what is it worth to the compiler writer and
the customers to have this case detected?

And again I ask what Java and C# do with this kind of code? I'm a programming
monoglot, so I can't test it myself. I have an opinion about what one might
reasonably expect of a compiler given the above code, but I'd like to know what
decision was reached by people designing languages for a living.

Scott
 
B

Balog Pal

Scott Meyers said:
And again I ask what Java and C# do with this kind of code? I'm a
programming monoglot, so I can't test it myself. I have an opinion about
what one might reasonably expect of a compiler given the above code, but
I'd like to know what decision was reached by people designing languages
for a living.

java langspec 3

8.4.7 Method Body

....

If a method is declared void, then its body must not contain any return
statement (§14.17) that has an Expression.

If a method is declared to have a return type, then every return statement
(§14.17) in its body must have an Expression. A compile-time error occurs if
the body of the method can complete normally (§14.1).

In other words, a method with a return type must return only by using a
return statement that provides a value return; it is not allowed to "drop
off the end of its body."

Note that it is possible for a method to have a declared return type and yet
contain no return statements. Here is one example:

class DizzyDean {

int pitch() { throw new RuntimeException("90 mph?!"); }

}
 
S

Scott Meyers

In other words, a method with a return type must return only by using a return
statement that provides a value return; it is not allowed to "drop off the end
of its body."

So presumably a Java compiler has to make sure there is no such path.

Scott
 
A

Alf P. Steinbach /Usenet

* Marcel Müller, on 02.11.2010 21:34:
Scott said:
Both C++98/03 and draft C++0x say this:


Does anybody know why this is undefined behavior instead of a hard error?

Beyond all pros and cons mentioned in the discussion the basic problem is in my
opinion the the language does not provide a way to declare a function that must
never return normally. The use cases would be exception factories as well as
some library functions like abort(). In kernel programming it might be useful
too. I missed that several times.
I have seen proprietary extensions for that purpose many years ago with Watcom C++.

However, maybe the standard did not want to force the impact that every compiler
must do a full code path analysis.

It has an interesting implication for lambda expressions. Lambdas declaring a
return type but returning nothing yield UB and, with the compilers I tested,
don't necessarily issue a warning:

auto f = []()->int { std::cout << "Oops, I forgot to return something"; };

I guess, that this is simply a bug (from the compilers point of view). The
lambda implementations are quite new.


Marcel

Btw. The second feature I regularly miss is to declare a function as side effect
free, which implies that the return value is a compile time constant if all
arguments are compile time constant.
I have seen (and used) this on a very old compiler for the inmos Transputer
platform. Looking at the generated code this turned out to give the compiler
room for very advanced optimizations.
Of course, it makes no difference as long as the code is inlined. But this is
not always an option, e.g. because PIMPL.

C++0x currently attempts to address both issues.

However, I have no compiler that compiles the code below. And Microsoft has sort
of promised to never implement C++0x attribute support. And as the code
illustrates apparently even the basic syntax is in flux (possibly Microsoft's
promise of non-compliance was because of the earlier syntax)...

But hopefully it gives the idea of roughly how such C++0x code might look:


<code>
// C++0x
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

#ifdef N3092_SYNTAX
bool throwX [[ noreturn ]] ( std::string const& s )
{
throw runtime_error( s );
}
#endif

#ifdef N3126_SYNTAX
[[ noreturn ]] bool throwX ( std::string const& s )
{
throw runtime_error( s );
}
#endif

constexpr
int mult( int a, int b )
{
return a*b;
}

int main()
{
char buf[ mult( 6, 7 ) ] = "Blah blah...";

cout << sizeof( buf ) << endl;
}
</code>

Cheers,

- Alf
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,709
Latest member
AustinMudi

Latest Threads

Top