为什么在不返回值的情况下流出非 void 函数的末尾不会产生编译器错误?

发布于 2024-08-08 20:20:03 字数 336 浏览 4 评论 0原文

自从我多年前意识到这默认情况下不会产生错误(至少在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

゛时过境迁 2024-08-15 20:20:03

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

如果控制到达main末尾而没有遇到return语句,则效果是执行

<前><代码>返回0;

注意,C++ 6.6.3/2 中描述的行为与 C++ 6.6.3/2 中描述的行为不同C.


如果您使用 -Wreturn-type 选项调用它,gcc 会给您一个警告。

-Wreturn-type 每当函数定义的返回类型满足以下条件时发出警告
默认为 int。还警告任何
没有返回值的 return 语句
在返回类型不是的函数中
无效(从末端掉落
函数体被视为返回
没有值),以及关于返回
带有 a 中的表达式的语句
返回类型为 void 的函数。

此警告由-Wall启用。


出于好奇,看看这段代码做了什么:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

这段代码具有正式未定义的行为,实际上它是调用约定< /a> 和架构 相关。在一个特定的系统上,使用一个特定的编译器,如果禁用优化,则返回值是最后一次表达式求值的结果,存储在该系统处理器的 eax 寄存器中。

这似乎是 GCC 内部禁用优化的结果,因为在这种情况下 如果需要任何返回值寄存器来实现语句,它会选择返回值寄存器。在 C++ 模式下启用优化后,GCC 和 clang 假定此执行路径无法访问,因为它包含未定义的行为。它们甚至不发出 ret 指令,因此执行会落入 .text 部分中的下一个函数。当然,未定义的行为意味着任何事情都可能发生。

C99 and C++ standards require non-void functions to return a value, except main. The missing return statement in main will be defined (to return 0). In C++ it's undefined behaviour if execution actually reaches the end of a non-void function other than main, 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 an if. It's only undefined behaviour if execution actually does get to the end without reaching a return 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 your main 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

Flowing off the end of a function [...] results in undefined behavior in a value-returning function.

§ 3.6.1/5

If control reaches the end of main without encountering a return statement, the effect is that of executing

return 0;

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.

-Wreturn-type Warn whenever a function is defined with a return-type that
defaults to int. Also warn about any
return statement with no return-value
in a function whose return-type is not
void (falling off the end of the
function body is considered returning
without a value), and about a return
statement with an expression in a
function whose return-type is void.

This warning is enabled by -Wall.


Just as a curiosity, look what this code does:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

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.

南城追梦 2024-08-15 20:20:03

默认情况下,gcc 不会检查所有代码路径是否返回值,因为通常这是无法完成的。它假设您知道自己在做什么。考虑一个使用枚举的常见示例:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

程序员知道,除非出现错误,否则此方法始终返回颜色。 gcc 相信您知道自己在做什么,因此它不会强迫您将 return 放在函数的底部。

另一方面,javac 尝试验证所有代码路径是否都返回一个值,如果无法证明它们都返回一个值,则会抛出错误。此错误是 Java 语言规范规定的。请注意,有时它是错误的,您必须放入不必要的 return 语句。

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

这是一个哲学上的差异。 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:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

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.

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

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.

晚风撩人 2024-08-15 20:20:03

您的意思是,为什么从返回值函数的末尾流出(即在没有显式返回的情况下退出)不是错误?

首先,在 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.

狼性发作 2024-08-15 20:20:03

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 中,如果您想要一个不返回结果的函数,您可以在没有显式返回类型的情况下定义它,并且只是不返回值:

#include <stdio.h>

do_something() {
    printf("Not returning a value\n");
}

int main() {
    do_something();
    return 0;
}

该函数实际上会返回一些垃圾 int 值呼叫者会悄悄地忽略它。

在现代 C 中,您可以这样写:

#include <stdio.h>

void do_something(void) {
    printf("Not returning a value\n");
}

int main(void) {
    do_something();
}

这保证调用者不能尝试使用返回值。从 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 the void keyword or type. And prior to the 1999 ISO C standard, C had the "implicit int" rule, meaning that you could declare or define a function without an explicit return type and it would return an int 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:

#include <stdio.h>

do_something() {
    printf("Not returning a value\n");
}

int main() {
    do_something();
    return 0;
}

The function would actually return some garbage int value which the caller would quietly ignore.

In modern C, you would write:

#include <stdio.h>

void do_something(void) {
    printf("Not returning a value\n");
}

int main(void) {
    do_something();
}

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 implicit int 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, or main results in undefined behavior, regardless of what the caller tries to do with the result.

我的影子我的梦 2024-08-15 20:20:03

在 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.

韬韬不绝 2024-08-15 20:20:03

我相信这是因为遗留代码(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).

层林尽染 2024-08-15 20:20:03

在某些有限和罕见的情况下,从非 void 函数的末尾流出而不返回值可能会很有用。就像下面的 MSVC 特定代码一样:

double pi()
{
    __asm fldpi
}

该函数使用 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:

double pi()
{
    __asm fldpi
}

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 calling exit, will not make the function execution flow off the end. And the compilers know it: writing a throw statement or calling exit in the empty body of pi() will stop any warnings or errors from a compiler.

南笙 2024-08-15 20:20:03

什么情况下不会产生错误?如果它声明了返回类型并且不返回某些内容,那么对我来说这听起来像是一个错误。

我能想到的一个例外是 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 a return 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 if return 0; is the last statement.

献世佛 2024-08-15 20:20:03

我收到该警告是因为我忘记添加声明
itr = itr -> 当前节点;
基本上缺少该语句,函数进入无限循环并且从未返回值,这就是我在编译期间收到该警告的原因

void* list_get(list* lst, int idx){

    node* itr = lst->head;
    
    if (idx >= lst->size){
        printf("list out of index");
        exit(1);
    }
    
    while(itr != NULL){
        if(itr->index == idx){
           return  itr->element;
        }
        itr = itr->currentNode;
     
    }


}

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

void* list_get(list* lst, int idx){

    node* itr = lst->head;
    
    if (idx >= lst->size){
        printf("list out of index");
        exit(1);
    }
    
    while(itr != NULL){
        if(itr->index == idx){
           return  itr->element;
        }
        itr = itr->currentNode;
     
    }


}
想挽留 2024-08-15 20:20:03

听起来您需要打开编译器警告:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$

Sounds like you need to turn up your compiler warnings:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$
未蓝澄海的烟 2024-08-15 20:20:03

在 c99 中这是一个约束违规,但在 c89 中则不是。对比:

c89:

3.6.6.4 return 语句

约束

带有表达式的 return 语句不得出现在
返回类型为 void 的函数。

c99:

6.8.6.4 return 语句

约束

带有表达式的return语句不得出现在返回类型为void的函数中。不带表达式的 return 语句只能出现在返回类型为 void 的函数中。

即使在 --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:

3.6.6.4 The return statement

Constraints

A return statement with an expression shall not appear in a
function whose return type is void .

c99:

6.8.6.4 The return statement

Constraints

A return statement with an expression shall not appear in a function whose return type is void. A return statement without an expression shall only appear in a function whose return type is void.

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 to
executing a return statement without an expression" (3.6.6.4). However, in c99 the behavior is undefined (6.9.1).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文