为什么在不返回值的情况下流出非 void 函数的末尾不会产生编译器错误?
自从我多年前意识到这默认情况下不会产生错误(至少在 GCC 中),我一直想知道为什么?
我知道您可以发出编译器标志来产生警告,但它不应该总是错误吗?为什么非 void 函数不返回有效值是有意义的?
评论中要求的示例:
#include <stdio.h>
int stringSize()
{
}
int main()
{
char cstring[5];
printf( "the last char is: %c\n", cstring[stringSize()-1] );
return 0;
}
...编译。
Ever since I realized many years ago, that this doesn't produce an error by default (in GCC at least), I've always wondered why?
I understand that you can issue compiler flags to produce a warning, but shouldn't it always be an error? Why does it make sense for a non-void function not returning a value to be valid?
An example as requested in the comments:
#include <stdio.h>
int stringSize()
{
}
int main()
{
char cstring[5];
printf( "the last char is: %c\n", cstring[stringSize()-1] );
return 0;
}
...compiles.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
C99 和 C++ 标准要求非
void
函数返回值,main
除外。main
中缺少的 return 语句将被定义(返回0
)。在 C++ 中,如果执行实际到达除main
之外的非void
函数的末尾,则为未定义行为,而在 C 中,如果调用者使用,则仅是 UB em> 返回值。这意味着函数看起来可能会到达末尾而不返回值,但实际上无法到达结束
}
。 John Kugelman 的回答 显示了一些示例,例如从if
一侧调用的 noreturn 函数。如果执行实际上已经结束而没有提前到达返回
,那么这只是未定义的行为。基本原理包括检查每个真实代码路径是否返回一个值非常困难(不知道哪些函数永远不会返回),因此编译像您的示例这样的函数并不违法,只是实际调用它你的main
确实如此。作为扩展,至少一个编译器 (MSVC) 允许设置返回值内联汇编,但大多数其他人仍然需要在使用内联
asm
的函数中使用 return 语句。来自 C++11 草案:
§ 6.6.3/2
§3.6.1/5
注意,C++ 6.6.3/2 中描述的行为与 C++ 6.6.3/2 中描述的行为不同C.
如果您使用 -Wreturn-type 选项调用它,gcc 会给您一个警告。
出于好奇,看看这段代码做了什么:
这段代码具有正式未定义的行为,实际上它是调用约定< /a> 和架构 相关。在一个特定的系统上,使用一个特定的编译器,如果禁用优化,则返回值是最后一次表达式求值的结果,存储在该系统处理器的 eax 寄存器中。
这似乎是 GCC 内部禁用优化的结果,因为在这种情况下 如果需要任何返回值寄存器来实现语句,它会选择返回值寄存器。在 C++ 模式下启用优化后,GCC 和 clang 假定此执行路径无法访问,因为它包含未定义的行为。它们甚至不发出 ret 指令,因此执行会落入 .text 部分中的下一个函数。当然,未定义的行为意味着任何事情都可能发生。
C99 and C++ standards require non-
void
functions to return a value, exceptmain
. The missing return statement inmain
will be defined (to return0
). In C++ it's undefined behaviour if execution actually reaches the end of a non-void
function other thanmain
, 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 anif
. It's only undefined behaviour if execution actually does get to the end without reaching areturn
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 yourmain
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
§ 3.6.1/5
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.
Just as a curiosity, look what this code does:
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.默认情况下,gcc 不会检查所有代码路径是否返回值,因为通常这是无法完成的。它假设您知道自己在做什么。考虑一个使用枚举的常见示例:
程序员知道,除非出现错误,否则此方法始终返回颜色。 gcc 相信您知道自己在做什么,因此它不会强迫您将 return 放在函数的底部。
另一方面,javac 尝试验证所有代码路径是否都返回一个值,如果无法证明它们都返回一个值,则会抛出错误。此错误是 Java 语言规范规定的。请注意,有时它是错误的,您必须放入不必要的 return 语句。
这是一个哲学上的差异。 C 和 C++ 是比 Java 或 C# 更宽容和更信任的语言,因此较新语言中的一些错误在 C/C++ 中是警告,并且默认情况下会忽略或关闭一些警告。
gcc does not by default check that all code paths return a value because in general this cannot be done. It assumes you know what you are doing. Consider a common example using enumerations:
You the programmer know that, barring a bug, this method always returns a color. gcc trusts that you know what you are doing so it doesn't force you to put a return at the bottom of the function.
javac, on the other hand, tries to verify that all code paths return a value and throws an error if it cannot prove that they all do. This error is mandated by the Java language specification. Note that sometimes it is wrong and you have to put in an unnecessary return statement.
It's a philosophical difference. C and C++ are more permissive and trusting languages than Java or C# and so some errors in the newer languages are warnings in C/C++ and some warnings are ignored or off by default.
您的意思是,为什么从返回值函数的末尾流出(即在没有显式返回的情况下退出)不是错误?
首先,在 C 语言中,只有当执行代码实际使用返回值时,函数是否返回有意义的内容才至关重要。当你知道大多数时候你都不会使用它时,也许该语言不想强迫你返回任何东西。
其次,显然语言规范不想强迫编译器作者检测和验证所有可能的控制路径是否存在显式返回(尽管在许多情况下这并不难做到)。此外,某些控制路径可能会导致非返回函数 - 编译器通常不知道这种特征。此类路径可能会成为恼人的误报的来源。
另请注意,在这种情况下,C 和 C++ 对行为的定义有所不同。在 C++ 中,仅从值返回函数的末尾流出始终是未定义的行为(无论函数的结果是否由调用代码使用)。在 C 中,仅当调用代码尝试使用返回值时,才会导致未定义的行为。
You mean, why flowing off the end of a value-returning function (i.e. exiting without an explicit
return
) is not an error?Firstly, in C whether a function returns something meaningful or not is only critical when the executing code actually uses the returned value. Maybe the language didn't want to force you to return anything when you know that you are not going to use it anyway most of the time.
Secondly, apparently the language specification did not want to force the compiler authors to detect and verify all possible control paths for the presence of an explicit
return
(although in many cases this is not that difficult to do). Also, some control paths might lead into to non-returning functions - the trait that is generally non known to the compiler. Such paths can become a source of annoying false positives.Note also, that C and C++ differ in their definitions of the behavior in this case. In C++ just flowing off the end of a value returning function is always undefined behavior (regardless of whether the function's result is used by the calling code). In C this causes undefined behavior only if the calling code tries to use the returned value.
C 和 C++ 有不同的规则。
C 语言规则是,如果返回非
void
值的函数的结束}
达到并且调用者尝试使用该值,行为是未定义的。只要调用者不使用该值,从函数末尾脱落就具有明确定义的行为。可能需要所有可能的控制路径在离开函数之前执行
return
语句,但 C 传统上并不要求编译器进行此类代码分析。 (许多编译器无论如何都会进行这种分析,并在适当的情况下发出警告。)允许脱离非
void
函数末尾的主要原因是历史性的。 K&RC(1978 年第一版 Kernighan 和 Ritchie 书中描述的版本,早于 1989 年 ANSI 和 1990 年 ISO C 标准)没有void
关键字或类型。在 1999 年 ISO C 标准之前,C 具有“隐式int
”规则,这意味着您可以声明或定义一个没有显式返回类型的函数,并且它将返回一个int
代码 > 结果。在 K&RC 中,如果您想要一个不返回结果的函数,您可以在没有显式返回类型的情况下定义它,并且只是不返回值:
该函数实际上会返回一些垃圾
int
值呼叫者会悄悄地忽略它。在现代 C 中,您可以这样写:
这保证调用者不能尝试使用返回值。从 C89/C90 开始,该语言仍然支持旧样式以避免破坏现有代码。当 C99 中删除隐式
int
规则时,对无法返回值的非void
函数的要求没有改变(并且大多数 C99 及更高版本的编译器仍然支持隐式默认情况下int
规则,可能带有警告,因此旧的 K&RC 代码仍然可以编译)。在 C++ 中,从构造函数、析构函数、
void
函数或main
之外的函数末尾流出会导致未定义的行为,无论调用者尝试做什么处理结果。C and C++ have different rules.
The language rule in C is that if the closing
}
of a function that returns a non-void
value is reached and the caller attempts to use that value, the behavior is undefined. Just falling off the end of the function has well defined behavior as long as the caller doesn't use the value.It would be possible to require all possible control paths to execute a
return
statement before leaving the function, but C traditionally has not required compilers to do that kind of code analysis. (Many compilers will do that analysis anyway and issue a warning if appropriate.)The main reason for allowing falling off the end of a non-
void
function is historical. K&R C (the version described in the 1978 first edition of Kernighan and Ritchie's book, before the 1989 ANSI and 1990 ISO C standard) did not have thevoid
keyword or type. And prior to the 1999 ISO C standard, C had the "implicitint
" rule, meaning that you could declare or define a function without an explicit return type and it would return anint
result.In K&R C, if you wanted a function that didn't return a result, you would define it without an explicit return type and simply not return a value:
The function would actually return some garbage
int
value which the caller would quietly ignore.In modern C, you would write:
which guarantees that the caller can't try to use the returned value. As of C89/C90, the language still supported the old style to avoid breaking existing code. When the implicit
int
rule was dropped in C99, the requirements on non-void
functions failing to return a value were not changed (and most C99 and later compilers still support the implicitint
rule by default, probably with a warning, so old K&R C code can still be compiled).In C++, flowing off the end of a function other than a constructor, a destructor, a
void
function, ormain
results in undefined behavior, regardless of what the caller tries to do with the result.在 C/C++ 下,不从声称返回某些内容的函数中返回是合法的。有许多用例,例如调用
exit(-1)
,或者调用它或引发异常的函数。如果您要求编译器不要这样做,即使它会导致 UB,编译器也不会拒绝合法的 C++。特别是,您要求不生成警告。 (默认情况下,Gcc 仍然会打开一些警告,但添加后,这些似乎与新功能一致,而不是针对旧功能的新警告)
更改默认的无参数 gcc 以发出一些警告可能是现有脚本或 make 系统的重大更改。精心设计的要么
-Wall
并处理警告,或者切换单个警告。学习使用 C++ 工具链是学习成为 C++ 程序员的障碍,但 C++ 工具链通常是由专家编写并为专家编写的。
It is legal under C/C++ to not return from a function that claims to return something. There are a number of use cases, such as calling
exit(-1)
, or a function that calls it or throws an exception.The compiler is not going to reject legal C++ even if it leads to UB if you are asking it not to. In particular, you are asking for no warnings to be generated. (Gcc still turns on some by default, but when added those seem to align with new features not new warnings for old features)
Changing the default no-arg gcc to emit some warnings could be a breaking change for existing scripts or make systems. Well designed ones either
-Wall
and deal with warnings, or toggle individual warnings.Learning to use a C++ tool chain is a barrier to learning to be a C++ programmer, but C++ tool chains are typically written by and for experts.
我相信这是因为遗留代码(C 从来不需要 return 语句,C++ 也是如此)。可能有巨大的代码库依赖于该“功能”。但至少有
-Werror=return-type
许多编译器(包括 gcc 和 clang)上的标志。
I believe this is because of legacy code (C never required return statement so did C++). There is probably huge code base relying on that "feature". But at least there is
-Werror=return-type
flag on many compilers (including gcc and clang).
在某些有限和罕见的情况下,从非 void 函数的末尾流出而不返回值可能会很有用。就像下面的 MSVC 特定代码一样:
该函数使用 x86 程序集返回 pi。与 GCC 中的汇编不同,我知道没有办法使用
return
来做到这一点而不涉及结果的开销。据我所知,主流 C++ 编译器至少应该对明显无效的代码发出警告。如果我将 pi() 的主体设为空,GCC/Clang 将报告警告,MSVC 将报告错误。
人们在一些答案中提到了异常和退出。这些都不是正当理由。无论是抛出异常,还是调用
exit
,都不会使函数执行流程结束。编译器知道这一点:在pi()
的空主体中编写 throw 语句或调用exit
将阻止编译器发出任何警告或错误。In some limited and rare cases, flowing off the end of a non-void function without returning a value could be useful. Like the following MSVC-specific code:
This function returns pi using x86 assembly. Unlike assembly in GCC, I know of no way to use
return
to do this without involving overhead in the result.As far as I know, mainstream C++ compilers should emit at least warnings for apparently invalid code. If I make the body of
pi()
empty, GCC/Clang will report a warning, and MSVC will report an error.People mentioned exceptions and
exit
in some answers. Those are not valid reasons. Either throwing an exception, or callingexit
, will not make the function execution flow off the end. And the compilers know it: writing a throw statement or callingexit
in the empty body ofpi()
will stop any warnings or errors from a compiler.什么情况下不会产生错误?如果它声明了返回类型并且不返回某些内容,那么对我来说这听起来像是一个错误。
我能想到的一个例外是
main()
函数,它根本不需要return
语句(至少在 C++ 中是这样;我也没有C 标准方便)。如果没有 return,它将表现为return 0;
是最后一条语句。Under what circumstances doesn't it produce an error? If it declares a return type and doesn't return something, it sounds like an error to me.
The one exception I can think of is the
main()
function, which doesn't need areturn
statement at all (at least in C++; I don't have either of the C standards handy). If there is no return, it will act as ifreturn 0;
is the last statement.我收到该警告是因为我忘记添加声明
itr = itr -> 当前节点;
基本上缺少该语句,函数进入无限循环并且从未返回值,这就是我在编译期间收到该警告的原因
I was getting that warning because i forgot to add the statement
itr = itr ->currentNode;
basically missing that statement, function get into infinte loop and was never returning a value, That's the reason i was getting that warning during the compilation time
听起来您需要打开编译器警告:
Sounds like you need to turn up your compiler warnings:
在 c99 中这是一个约束违规,但在 c89 中则不是。对比:
c89:
c99:
即使在
--std=c99
模式下,gcc 也只会抛出警告(尽管不需要启用额外的-W
标志,如默认情况下或 c89/90 中所要求的那样) )。编辑并添加在 c89 中,“到达终止函数的
}
相当于执行不带表达式的
return
语句”(3.6.6.4)。但是,在 c99 中,该行为未定义(6.9.1)。It is a constraint violation in c99, but not in c89. Contrast:
c89:
c99:
Even in
--std=c99
mode, gcc will only throw a warning (although without needing to enable additional-W
flags, as is required by default or in c89/90).Edit to add that in c89, "reaching the
}
that terminates a function is equivalent toexecuting a
return
statement without an expression" (3.6.6.4). However, in c99 the behavior is undefined (6.9.1).