views:

251

answers:

3

Okay, little oddity I discovered with my C++ compiler.

I had a not-overly complex bit of code to refactor, and I accidentally managed to leave in a path that didn't have a return statement. My bad. On the other hand, this compiled, and segfaulted when I ran it and that path was hit, obviously.

Here's my question: Is this a compiler bug, or is there no guarantee that a C++ compiler will enforce the need for a return statement in a non-void return function?

Oh, and to be clear, in this case it was an unecessary if statement without an accompanying else. No gotos, no exits, no aborts.

+10  A: 

There is no guarantee that a C++ compiler will enforce that. A C++ function could jump out of its control flow by mechanisms unknown to the compiler. Context switches when C++ is used to write an OS kernel is an example of that. An uncaught exception thrown by a called function (whose code isn't necessarily available to the caller) is another one.

Some other languages, like Java, explicitly enforce that with knowledge available at compile time, all paths return a value. In C++ this isn't true, as is with many other occasions in the language, like accessing an array out of its bounds isn't checked either.

Johannes Schaub - litb
"A C++ function could jump out of its control flow by mechanisms unknown to the compiler." - yes, but the flow will be jumped back into. A context switch switchs back, after all. I really don't see what this has to do with return values. The only case the function will not be reverted to is if an exception is thrown.
anon
@Neil not necessary. A call to `exit`, for instance, will never jump back.
Johannes Schaub - litb
@Johannes OK, and abort() and terminate(). But the compiler cannot see that these negate the need for a return, unless it treats them as "special" in some way.
anon
@Neil exactly. that's why it can't enforce you to return a value, because it doesn't know they negate the need for a return, like you perfectly noticed. It's assuming that it doesn't return and trusts you.
Johannes Schaub - litb
@Johannes On the very rare occasions that exit/abort/terminate get called (I don't think I've ever called any of them in my career) I would say having to add a "fake" return statement is preferable to the alternative of not diagnosing missing return statements, which is a far, far more common issue.
anon
@litb: OMG, why? It regularly does enforce invariants on impossible paths in the code, why couldn't it enforce returning a value even when that coude would never be used? See http://ideone.com/MHDKl
jpalecek
@Neil, well I agree with you to full degree. In my own pet-language, i would enforce this too. But it seems that C and C++ are the sort of language that don't care about that.
Johannes Schaub - litb
@jpalecek it looks to me that "goto" problem is easily fixed by wrapping a block around the variable declaration. In the function case, adding a "return someValue;" possibly blows up the generated code by some margin (not what C and C++' design principles target). I suspect the C++ guys's intention was that people could add a "abort();" on impossible to take paths, instead of a dummy return (which i personally really try to avoid, because it's dead code).
Johannes Schaub - litb
@Johannes While I have your attention, on a somewhat tangentially related issue, did you see this question http://stackoverflow.com/questions/3179494/under-what-circumstances-are-c-destructors-not-going-to-be-called - do you have a read on how signals affect object lifetimes?
anon
@Neil please inspect my deleted answer on that thread :)
Johannes Schaub - litb
@Johannes I missed that, but it seems a bit, shall we say, equivocal. Do you have some C++ Standard reference for this? Maybe reopen the answer in that thread?
anon
@Neil see 18.7 and 3.6.3/4 (and 18.3/3).
Johannes Schaub - litb
@jpalecek: it's easy to return a dummy `int`. But what if a function returns a far more complex type? You can't in general assume that it's even possible to create a dummy object. Even where you can, it may involve writing a lot of code. Or you end up with a `*(T*)NULL` hack. Really, that doesn't improve anything over a missing return.
MSalters
I suspect that instead of a trailing "return", they could equally accept a trailing "throw". That would not require simulating a `T` by doing `*(T*)0` or something.
Johannes Schaub - litb
@MSalters: I usually do `throw 0;` or something like that in this case, as litb suggests.
jpalecek
+3  A: 

The compiler doesn't enforce this because you have knowledge about what paths are practically possible that the compiler doesn't. The compiler typically only knows about that particular file, not others that may affect the flow inside any given function. So, it isn't an error.

In Visual Studio, though, it is a warning. And we should pay attention to all warnings.... right? :)

Edit: There seems to be some discussion about when this could happen. Here's a modified but real example from my personal code library;

enum TriBool { Yes, No, Maybe };

TriBool GetResult(int input) {
    if (TestOne(input)) {
        return Yes;
    } else if (TestTwo(input)) {
        return No;
    }
}

Bear with me because this is old code. Originally there was an "else return maybe" in there. :) If TestOne and TestTwo are in a different compilation unit then when the compiler hits this code, it can not tell if TestOne and TestTwo could both return false for a given input. You, as the programmer that wrote TestOne and TestTwo, know that if TestOne fails then TestTwo will succeed. Maybe there are side effects of those tests so they have to be done. Would it be better to write it without the "else if"? Maybe. Probably. But the point is that this is legal C++ and the compiler can't know if it is possible to exit without a return statement. It is, I agree, ugly and not good coding but it is legal and Visual Studio will give you a warning but it will compile.

Remember that C++ isn't about protecting you from yourself. It is about letting you do what your heart desires within the constraints of the language even if that includes shooting yourself in the foot.

Jere.Jones
This makes no sense. The compiler musty have knowledge of what paths are possible in order to compile the code.
anon
@Neil i think he has a point. The compiler doesn't always know at *compile time*. Like you had no clue when you posted your comment that i would post an answer to it. But now you know, and possibly have precautions to answer in turn, or to not. Or you are totally surprised, like the C++ will be, and do undefined things :)
Johannes Schaub - litb
That is nonsense IMHO. The control flow graph of any given function is perfectly known by the compiler after analysing its code. It might be the case that some edges are never traversed while executing the code (think about asserts), but the compiler treats it as such and all it causes is just an (overly)conservative guess.
jpalecek
Should the compiler test for real all possible inputs for the code? So indeed at compile time it can't know, for all "paths", which can actualy be taken and which can not. So there are cases where it can't be able to identify "a path" that ends with no a return result? I still have to imagine which are these cases, however.
ShinTakezou
+10  A: 

Personally I think this should be an error:

int f() {
}

int main() {
    int n = f();
    return 0;
}

but most compilers treat it as a warning, and you may even have to use compiler switches to get that warning. For example, on g++ you need -Wall to get:

[neilb@GONERIL NeilB]$ g++ -Wall nr.cpp
nr.cpp: In function 'int f()':
nr.cpp:2: warning: no return statement in function returning non-void

Of course, with g++ you should always compile with at least -Wall anyway.

anon