131

I found this in one of my libraries this morning:

static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
    tvec3::Min(a,b,out);
    out.w = min(a.w,b.w);
}

I'd expect a compiler error because this method doesn't return anything, and the return type is not void.

The only two things that come to mind are

  • In the only place where this method is called, the return value isn't being used or stored. (This method was supposed to be void - the tvec4 return type is a copy-and-paste error)

  • a default constructed tvec4 is being created, which seems a bit unlike, oh, everything else in C++.

I haven't found the part of the C++ spec that addresses this. References (ha) are appreciated.

Update

In some circumstances, this generates an error in VS2012. I haven't narrowed down specifics, but it's interesting, nonetheless.

3Dave
  • 28,657
  • 18
  • 88
  • 151
  • 42
    It's undefined behaviour. – Simple Dec 16 '13 at 15:17
  • 11
    compile it with `-Wall` and `-pedantic` should give a warning about it. – greatwolf Dec 16 '13 at 15:18
  • 3
    I usually get warnings when return type is specified but no return value given, not compiler errors – mathematician1975 Dec 16 '13 at 15:18
  • 7
    You can get C++ and Clang to treat this as an error with `-Wreturn-type -Werror`. – juanchopanza Dec 16 '13 at 15:21
  • @DavidRodrigues `min()` is `std::min()` from ``. `Min` (caps) is a member function. – 3Dave Dec 16 '13 at 15:21
  • Related question http://stackoverflow.com/questions/9924570/gcc-options-warning-on-non-void-functions-without-a-return-statement – juanchopanza Dec 16 '13 at 15:23
  • 3
    C++ doesn't make sense. Why would one want that to be compileable at all? – JMCF125 Dec 16 '13 at 19:27
  • 5
    @JMCF125: I think it's because it's not possible for the compiler to tell if the program would ever reach the end of the function. If `min` always throws an exception, then the lack of a return value is not an issue, and probably deliberate. – Mooing Duck Dec 17 '13 at 02:00
  • 2
    @Mooing, in, say, Java, it wouldn't compile. If the compiler can't tell whether it reaches the end or not, it simply shouldn't compile. In fact, it would just have to check if a single `return` statement exists, with an object of type `tvec4`. In this case it should complain. Otherwise, why would one want static typing at all? – JMCF125 Dec 17 '13 at 13:42
  • @JMCF125: I also disagree with what C++ does here, instead of undefined behavior I think it ought to be defined has calling `std::terminate`. Has nothing to do with typing. – Mooing Duck Dec 17 '13 at 17:19
  • 3
    @MooingDuck, why treat it as run time error when you can treat it as a compile time error? I'd prefer to have the error on compile time. (that if I got `std::terminate` right, I just googled it, as I don't know C++ much) – JMCF125 Dec 17 '13 at 20:06
  • @JMFC125: what would you return in the case where it isn't logical to return anything? (example: http://coliru.stacked-crooked.com/a/ffe72ece096e2406) – Mooing Duck Dec 17 '13 at 20:11
  • 1
    @Mooing Duck: It isn't logical to put anythig there. Method is already finished. Compiler should complain about unreachable code. – Piotr Perak Dec 17 '13 at 21:50
  • @Peri: That's exactly what both I and the C++ committee think, though JMCF125 seems to think otherwise. – Mooing Duck Dec 17 '13 at 21:58
  • 1
    @MooingDuck: I think we have misunderstanding here :) I say that C++ compiler sucks not generating error in code from question. There is no return and return type is not void. So I agree with JMFC125. You present example that doesn't make sense and shouldn't compile or at least should generate warning as it does in C#. Hell even good code editors show that as warning as soon as you type it. You don't even have to compile anything. – Piotr Perak Dec 17 '13 at 23:24
  • @Peri: Most C++ compilers do give a warning. I still hold that there are situations where no return value makes sense, but since C# only has references, you guys always have the option of just returning null to make the warning go away. C++ has more options than just references, but that means there's no easy workaround. – Mooing Duck Dec 17 '13 at 23:44
  • @GraphicsResearch Though it's not visible in the code snippet (aside from the `static` prefix, which doesn't make a lot of sense outside of a class or struct), this is an inline method attached to a struct. Also, I'd argue that methods, functions and procedures are all specific cases of a routine. (Also, this is kind of arguing semantics.) – 3Dave Nov 05 '14 at 20:19
  • @DavidLively okay then, I'll agree it's a method. I'd disagree about the rest though. People are reasonably consistent about using "method" for members, and the math definition of a function gives it the useful distinction of returning something.¶ People are inconsistent enough with the rest to prevent the existence of a Right Thing in other cases (so (sub)routine, procedure, etc. would have been fine too if it weren't already a method). – geometrian Nov 05 '14 at 20:26
  • @GraphicsResearch But your argument (correctly) states that methods and functions have significant overlap (in a Venn diagram sense). At the risk of starting an (admittedly interesting) conversation in the comments, it seems that the concepts of functions and methods are orthogonal. A procedure is loosely a void function (in the programming sense, not math, where "void function" makes no sense), and a method is either of those that happens to be part of a class. `Routine` just means a sequence of instructions with a label, which could be called with or without a return (ie,ISR with `while(1)`) – 3Dave Nov 05 '14 at 20:42
  • @DavidLively Since this question is fairly old by SO standards, I've no regrets continuing discussion in comments. But we could move to chat instead. "_A procedure is loosely a void function . . . and a method is either of those that happens to be part of a class._" This is my view as well. AFAIK "routine" is older than the "function"/"procedure" distinction, which comes from Pascal. In any case, I don't think "(sub)routine" and "procedure" are consistently enough used to justify use as technical terms--but, the point is "function" and "method" _are_. – geometrian Nov 05 '14 at 23:17

7 Answers7

156

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 11
    For the record, VC generates either [C4716](http://msdn.microsoft.com/en-us/library/ft5xye74.aspx) or [C4715](http://msdn.microsoft.com/en-us/library/6deaf4k9.aspx) in such a case. The former is even treated as a compiler error by default. – ComicSansMS Dec 16 '13 at 15:46
  • @ComicSansMS yeah I was trying to get an error using [rextester](http://rextester.com/runcode) and now I just realized I can only receive the error if I remove `static` which is interesting. – Shafik Yaghmour Dec 16 '13 at 15:57
  • 3
    If the `static` is there, but the function isn't referenced elsewhere in the file, it's possible the compiler is optimizing the function out entirely, and not bothering to check for a `return`. By removing `static`, you make the function referenceable from other files. That means the compiler can't optimize it away, and gets to the part where it looks for a `return`. – dpassage Dec 17 '13 at 02:19
  • 2
    @dpassage confirmed that it was being optimized out. – Shafik Yaghmour Dec 17 '13 at 02:38
  • @ComicSansMS I updated my answer to include the details for Visual Studio, thanks for pointing that out. – Shafik Yaghmour Dec 17 '13 at 02:39
  • Note for C this is not undefined behavior unless you use the value. Compiler [optimize differently for this difference between C and C++](https://twitter.com/shafikyaghmour/status/991781073322360832) – Shafik Yaghmour May 08 '18 at 21:28
  • In the C++20 draft, the first citation has changed and is in 9.6.3 – Thomas Weller Nov 28 '22 at 08:18
58

Maybe some elaboration on the why part of the question:

As it turns out, it is actually quite hard† for a C++ compiler to determine whether a function exits without a return value. In addition to the code paths that end in explicit return statements and the ones that fall off the end of the function, you also have to consider potential exception throws or longjmps in the function itself, as well as all of its callees.

While it is quite easy for a compiler to identify a function that looks like it might be missing a return, it is considerably harder to prove that it is missing a return. In order to lift compiler vendors of this burden, the standard does not require this to generate an error.

So compiler vendors are free to generate a warning if they are quite sure that a function is missing a return and the user is then free to ignore/mask that warning in those rare cases where the compiler was actually wrong.

†: In the general case, this is equivalent to the halting problem, so it is actually impossible for a machine to decide this reliably.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • I see how `if` and `switch` statements can make this hard, but throws and `longjmp`? Could you elaborate? – Christopher Creutzig Dec 16 '13 at 19:03
  • 2
    @ChristopherCreutzig Simply imagine calling a function that resides in a different compilation unit (or even a dynamic library). Since you do not see the code and exceptions are not part of the interface in C++ it's impossible to tell if and how such a function call returns. – ComicSansMS Dec 16 '13 at 19:16
  • 2
    If my routine is left with an exception, it obviously does not return any value. But it’s not supposed to, and finding out that in this case it does not isn’t hard at all. The same is true for `longjmp` – there won’t be any value, and no-one to try and pick it up. – Christopher Creutzig Dec 16 '13 at 19:23
  • 2
    @ChristopherCreutzig True, but the point is you would not want a function to refuse compiling just because one of its execution paths returns with an exception instead of a return value. Since the compiler cannot reliably detect exceptions, it cannot fail compilation just because an execution path seems to be missing a return - there might still be an exception hidden somewhere along that path. – ComicSansMS Dec 16 '13 at 19:29
  • 1
    I think I got it, sorry for being a bit daft. You mean a function that ends in a call to `throwAnError()` or something like that. Yes, those are hard – I usually just end up adding a dummy return with `// make the compiler happy`. Not pretty, but for me, it’s a small price for having the warning error in the cases where I’d run into UB. – Christopher Creutzig Dec 16 '13 at 19:39
  • 2
    @ChristopherCreutzig Exactly. I often run into this problem with large switch-case statements where each case label ends in a return. If I then add a default label that triggers an error condition, just in case I accidentally missed a case earlier, the compiler is often not able to tell that the default case also never returns. So I get a warning about a missing return at the end of the function and have to insert one of those dummy returns you mentioned. – ComicSansMS Dec 16 '13 at 19:48
  • 2
    That is what the ``noreturn`` attribute in gcc is for. Just in case you are only using compilers supporting that. Functions having that attribute are considered as non-returning. AFAIK ``assert()`` from ``` is annotated that way, which allows using it your mentioned ``default:`` – Jonas Schäfer Dec 16 '13 at 22:10
  • 2
    @JonasWielicki: There is a standard attribute for that: `[[noreturn]]`. – Xeo Dec 17 '13 at 08:31
  • 6
    it might be prudent to mention that deciding if a function gets to the end of it's block or does an early return (either by exception of longjmp) is equivalent to the [halting problem](http://en.wikipedia.org/wiki/Halting_problem) which is undecidable in the general case – ratchet freak Dec 17 '13 at 10:32
  • I'd argue that it's possible to do a text search for the word `return` within the body of the function to determine if it returns anything at any point, which is sufficient to address this particular issue (though admittedly not to determine if it *always* returns something). That would be a very, very poor approach, but somewhere between "don't worry about it" and searching for `return` at the parse stage should be a good solution. :) – 3Dave Nov 05 '14 at 20:16
  • @ComicSansMS: excellent answer really. +5 for nice explanation – Destructor Jun 27 '15 at 19:03
22

Compile your code with -Wreturn-type option:

$ g++ -Wreturn-type source.cpp

This will give you warning. You can turn the warning into error if you use -Werror too:

$ g++ -Wreturn-type -Werror source.cpp

Note that this will turn all warnings into errors. So if you want error for specific warning, say -Wreturn-type, just type return-type without -W part as:

$ g++ -Werror=return-type source.cpp

In general you should always use -Wall option which includes most common warnings — this includes missing return statement also. Along with -Wall, you can use -Wextra also, which includes other warnings not included by -Wall.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    That only works for certian compilers. He didn't say what compiler he was using. – T.E.D. Dec 16 '13 at 19:57
  • 11
    @T.E.D.: Then, read the doc of your compiler. Most mainstream compilers equivalent options. (BTW, he should have said what compiler he was using, if it is interested to know a *particular* compiler behavior). – Nawaz Dec 16 '13 at 20:00
22

Maybe some additional elaboration on the why part of the question.

C++ was designed so that a very large body of pre-existing body of C code compiles with minimum amount of changes. Unfortunately, C itself was paying a similar duty to earliest pre-standard C which did not even have the void keyword and instead relied on a default return type of int. C functions usually did return values, and whenever code superficially similar to Algol/Pascal/Basic procedures was written without any return statements, the function was, under the hood, returning whichever garbage was left on the stack. Neither the caller nor the callee assigns the value of the garbage in a reliable way. If the garbage is then ignored by every caller, everything is fine and C++ inherits the moral obligation to compile such code.

(If the returned value is used by the caller, the code may behave non-deterministically, similar to processing of an uninitialized variable. Could the difference be reliably identified by a compiler, in a hypothetical successor language to C? This is hardly possible. The caller and the callee may be in different compilation units.)

The implicit int is just a part of the C legacy involved here. A "dispatcher" function might, depending on a parameter, return a variety of types from some code branches, and return no useful value from other code branches. Such a function would generally be declared to return a type long enough to hold any of the possible types and the caller might need to cast it or extract it from a union.

So the deepest cause is probably the C language creators' belief that procedures that do not return any value are just an unimportant special case of functions that do; this problem got aggravated by the lack of focus on type safety of function calls in the oldest C dialects.

While C++ did break compatibility with some of the worst aspects of C (example), the willingness to compile a return statement without a value (or the implicit value-less return at the end of a function) was not one of them.

Community
  • 1
  • 1
Jirka Hanika
  • 13,301
  • 3
  • 46
  • 75
  • 2
    +1 Clear explanation. – 3Dave Dec 17 '13 at 15:09
  • 4
    Interesting to note that in C99 and C11 this is only undefined if the caller attempts to use the return value. – Shafik Yaghmour Dec 17 '13 at 15:21
  • 1
    +1 Though this question has been asked and answered many times in SO, this answer IMHO adds some valuable clarity, thanks @Jirka. – Abhay Dec 19 '13 at 05:07
  • This is interesting rationale but I don't think it is the main reason. As I see it, the main reason is so that the compiler is not forced to reject correct code. For example, a function whose body is an infinite loop and there is a `return` statement inside the loop in response to some condition. A `return x;` after the loop would be redundant. It's easy to imagine gradually more complicated versions of this scenario where it gets harder and harder for the compiler to see if the loop really is infinite or not, but easy for the programmer to see. – M.M Sep 11 '15 at 03:41
  • @M.M - Are you realizing that C++ will even allow an explicit `return` statement without any expression in a non-void function? And that C# and Java solved the problem you are describing much better than C++ did (unless C compatibility is taken as a requirement)? – Jirka Hanika Sep 11 '15 at 11:56
  • @JirkaHanika I consider the Java "solution" worse than the C++ one. – M.M Sep 11 '15 at 13:02
12

As already mentioned, this is undefined behavior and will give you a compiler warning. Most places I've worked require you to turn on compiler settings to treat warnings as errors - which enforces that all your code must compile with 0 errors and 0 warnings. This is a good example of why that is a good idea.

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
2

This is more of the standard C++ rule/feature which tends to be flexible with things and which tends to be more close to C.

But when we talk of the compilers, GCC or VS, they are more for professional usage and for variety of development purposes and hence put more strict development rules as per your needs.

That makes sense also, my personal opinion, because the language is all about features and its usage whereas compiler defines the rules for optimal and best way of using it as per your needs.

As mentioned in above post, compiler sometimes gives the error, sometimes gives warning and also it has the option of skipping these warning etc, indicating the freedom to use the language and its features in a way that suits us best.

Bhupesh Pant
  • 4,053
  • 5
  • 45
  • 70
-1

Along with this there are several other questions mentioning this behaviour of returning a result without having a return statement. One simple example would be:

int foo(int a, int b){ int c = a+b;}

int main(){
   int c = 5;
   int d = 5;

   printf("f(%d,%d) is %d\n", c, d, foo(c,d)); 

   return 0;
}

Could this anomaly be due stack properties and more specifically:

Zero-Address Machines

In zero-address machines, locations of both operands are assumed to be at a default location. These machines use the stack as the source of the input operands and the result goes back into the stack. Stack is a LIFO (last-in-first-out) data structure that all processors support, whether or not they are zero-address machines. As the name implies, the last item placed on the stack is the first item to be taken out of the stack. All operations on this type of machine assume that the required input operands are the top two values on the stack. The result of the operation is placed on top of the stack.

In addition to that, for accessing memory to read and write data same registers are used as data source and destination(DS (data segment) register), that store first the variables needed for the calculation and then the returned result.

Note:

with this answer I would like to discuss one possible explanation of the strange behaviour at machine (instruction) level as it has already a context and its covered in adequately wide range.

Ziezi
  • 6,375
  • 3
  • 39
  • 49
  • 1
    doesn't print 10 for me. In fact it is undefined behaviour. – M.M Sep 11 '15 at 03:38
  • Neat example, but I'm wondering what compiler you used? – 3Dave Sep 11 '15 at 05:17
  • @M.M question inspired by: http://stackoverflow.com/questions/32513793/c-and-c-functions-without-a-return-statement/32513841#32513841 and: http://stackoverflow.com/questions/10858391/why-do-programs-in-c-compile-even-when-the-return-statement-is-missing – Ziezi Sep 11 '15 at 07:52
  • Those examples rely on particular architectures, compilers and compiler settings. I don't see what your answer has to do with the question. – M.M Sep 11 '15 at 07:58
  • @M.M my answer is covering the _architecture part_ and provides one possible explanation of the strange occurrence on machine level. You are right that the formulation could be and will be edited. – Ziezi Sep 11 '15 at 08:16