可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has an answer here:
-
Why does flowing off the end of a non-void function without returning a value not produce a compiler error?
8 answers
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.
回答1:
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.
回答2:
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 longjmp
s 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.
回答3:
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
.
回答4:
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.
回答5:
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.
回答6:
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.
回答7:
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.