ChatGPT解决这个技术问题 Extra ChatGPT

退出和返回有什么区别?

从 C 程序中的任何位置调用时,C 编程中的 return 和 exit 语句有什么区别?

我删除了“关闭为重复项”,因为选择的重复项同时标记了 C 和 C++,并且没有必要将人们与 C++ 问题混淆(尽管与 C 问题不同,但有点相似)。副本是 return statement vs exit() in main()?

P
Peter Cordes

return 从当前函数返回;它是一个语言关键字,例如 for 或 break。

exit() 终止整个程序,无论您从哪里调用它。 (在刷新 stdio 缓冲区等之后)。

两者做(几乎)相同的事情的唯一情况是在 main() 函数中,因为从 main 的返回执行 exit()

在大多数 C 实现中,main 是一个真正的函数,由一些启动代码调用,执行类似于 int ret = main(argc, argv); exit(ret); 的操作。如果 main 返回,C 标准保证会发生与此等效的事情,但是实现会处理它。

return 示例:

#include <stdio.h>

void f(){
    printf("Executing f\n");
    return;
}

int main(){
    f();
    printf("Back from f\n");
}

如果你执行这个程序,它会打印:

从 f 执行 f Back

exit() 的另一个示例:

#include <stdio.h>
#include <stdlib.h>

void f(){
    printf("Executing f\n");
    exit(0);
}

int main(){
    f();
    printf("Back from f\n");
}

如果你执行这个程序,它会打印:

执行 f

你永远不会得到“从 f 回来”。另请注意调用库函数 exit() 所必需的 #include <stdlib.h>

还要注意 exit() 的参数是一个整数(它是启动器进程可以获得的进程的返回状态;常规用法是 0 表示成功或任何其他值表示错误)。

return 语句的参数是函数的返回类型。如果函数返回 void,则可以省略函数末尾的返回。

最后一点,exit() 有两种形式 _exit()exit()。表单之间的区别在于 exit()(并从 main 返回)在真正终止进程之前调用使用 atexit()on_exit() 注册的函数,而 _exit()(来自 #include <unistd.h>,或其同义词 _Exit from #include <stdlib.h> ) 立即终止进程。

现在还有一些特定于 C++ 的问题。

C++ 在退出函数时比 C 执行更多的工作(return-ing)。具体来说,它调用超出范围的本地对象的析构函数。在大多数情况下,程序员不会在乎进程停止后程序的状态,因此不会有太大的不同:分配的内存将被释放,文件资源关闭等等。但是你的析构函数是否执行 IO 可能很重要。例如,在调用 exit 时不会刷新本地创建的自动 C++ OStream,并且您可能会丢失一些未刷新的数据(另一方面,静态 OStream 将被刷新)。

如果您使用良好的旧 C FILE* 流,则不会发生这种情况。这些将在 exit() 上刷新。实际上,规则与注册退出函数的规则相同,在所有正常终止时都会刷新 FILE*,包括 exit(),但不会调用 _exit() 或 abort()。

您还应该记住,C++ 提供了第三种退出函数的方法:抛出异常。这种退出函数的方式会调用析构函数。如果它在调用者链中的任何地方都没有被捕获,则异常可以上升到 main() 函数并终止进程。

如果您在程序中的任何位置从 main()exit() 调用 return,则将调用静态 C++ 对象(全局)的析构函数。如果程序使用 _exit()abort() 终止,它们将不会被调用。 abort() 在调试模式下最有用,目的是立即停止程序并获取堆栈跟踪(用于事后分析)。它通常隐藏在仅在调试模式下有效的 assert() 宏后面。

exit() 什么时候有用?

exit() 表示您想立即停止当前进程。当我们遇到某种无法恢复的问题时,它可能对错误管理有一些用处,这些问题不允许您的代码再做任何有用的事情。当控制流很复杂并且错误代码必须一直向上传播时,它通常很方便。但请注意,这是不好的编码习惯。在大多数情况下,静默结束进程是更糟糕的行为,应该首选实际的错误管理(或者在 C++ 中使用异常)。

如果在库中直接调用 exit() 尤其糟糕,因为它会使库用户注定要失败,并且应该由库用户选择是否实现某种错误恢复。如果您想举例说明为什么从库调用 exit() 不好,它会导致例如人们询问 this question

在支持它的操作系统上,使用 exit() 作为结束由 fork() 启动的子进程的方式是无可争议的合法用途。回到 fork() 之前的代码通常是个坏主意。这就是解释为什么 exec() 系列的函数永远不会返回给调用者的理由。


exit() 不是系统调用
我通常在 main() 中使用 return。当然,我在 main() 的末尾使用 return 0; — 我有时在函数体中使用 exit();。我不喜欢 C99 规则关于从 main() 的末尾掉下来等同于 return 0; 的规则;这是一个愚蠢的特殊情况(尽管 C++ 率先造成损害)。
注意:C11 标准具有附加功能 at_quick_exit()_Exit()(也在 C99 中)和 quick_exit()_exit() 函数来自 POSIX,但本质上与 _Exit() 相同。
如果您想要一个例子来说明为什么退出图书馆是不好的:它会让人们问这个问题stackoverflow.com/q/34043652/168175。然后大概要么使用黑客,要么无缘无故地使用 IPC
@Milan:一些 IO 被缓冲,这是 OStream 的情况。在这种情况下,flushed 意味着数据实际上已发送到控制台/写入磁盘,而不仅仅是保存在缓冲区中。未刷新意味着数据仍在应用程序的某些内存缓冲区中,但尚未发送到系统。在 C++ 文档中,他们将数据发送到的底层系统对象称为“受控序列”。 OStream 上有一个明确的刷新方法可以做到这一点。
h
hurufu

我写了两个程序:

int main(){return 0;}

#include <stdlib.h>
int main(){exit(0)}

执行 gcc -S -O1 后。这是我在装配时发现的(仅重要部分):

main:
    movl    $0, %eax    /* setting return value */
    ret                 /* return from main */

main:
    subq    $8, %rsp    /* reserving some space */
    movl    $0, %edi    /* setting return value */
    call    exit        /* calling exit function */
                        /* magic and machine specific wizardry after this call */

所以我的结论是:尽可能使用 return,并在需要时使用 exit()


这是一个很好的答案,除了结论;恕我直言,它激发了相反的动机:我们可以假设 ret 指令返回到可能完成一些额外工作的点,但最终无论如何都会调用 exit() 函数 - 或者如何避免做什么 exit() 做什么?这意味着前者只是做了一些额外的“重复”工作,而对第二种解决方案没有任何好处,因为无论如何在最后肯定会调用 exit() 。
@Max:不禁止从您自己的程序中递归调用 main 。从此以后,您不应假设从 main 返回会立即退出,这会破坏代码语义。在某些(罕见)情况下,它甚至很有用。例如,在将代码入口点更改为与 main 不同的东西之后,在调用 main 之前帮助准备/清除一些上下文。这甚至可以通过一些运行时代码注入方法来完成。当然,当它是你的程序时,你可以做任何你喜欢或认为更容易阅读的事情。
@kriss:这是一个很好的观点,之前没有提到。尽管我认为 main() 被递归调用是极其罕见的,但这种可能性比其他任何事情都更清楚地说明了 return 和 exit(0) 之间的区别。
D
Dumb Guy

在 C 中,在程序的启动函数(可以是 main()wmain()_tmain() 或编译器使用的默认名称)中使用时没有太大区别。

如果您在 main() 中使用 return,则控制权会返回到最初启动您的程序的 C 库中的 _start() 函数,然后无论如何都会调用 exit()。因此,您使用哪一个并不重要。


这很重要。 exit() 立即终止程序,无论它在哪里被调用。 return 只退出当前函数。他们做同样事情的唯一位置是在 main()
谢谢,我已经修正了措辞。它不一定只在 main() 中,因为并非所有编译器都对启动函数使用相同的函数名。
我猜你把你所有的程序都写在一个大的主函数中? ;-)
答案不完整,但仍然提供信息。
k
kapil

return 语句退出当前函数,exit() 退出程序

they are the same when used in main() function

return 也是一个语句,而 exit() 是一个需要 stdlb.h 头文件的函数


如果程序中的任何函数调用 main ,它们是相同的。这很少见;大多数程序不使用递归或可重入主程序,但如果我们谈论的是语言细节,那么这是有可能的。
J
Jonathan Leffler

在大多数情况下,在 C 程序中使用 return 和调用 exit() 来终止 main() 之间没有区别。

不同之处在于您是否创建了代码,该代码将在您从 main() 返回后执行,该代码依赖于 main() 的本地变量。体现自身的一种方式是使用 setvbuf()

int main(void)
{
    char buffer[BUFSIZ];
    setvbuf(stdout, buffer, _IOFBF, BUFSIZ);
    …code using stdout…
    return 0;
}

在此示例中,当 main() 返回时,通过 setvbuf() 提供的缓冲区超出范围,但刷新和关闭 stdout 的代码将尝试使用该缓冲区。这会导致未定义的行为。

另一种机制是使用从 main() 访问数据的函数调用 atexit() — 通过指针。这更难设置,因为通过 atexit() 机制调用的函数没有给出任何参数。所以,你必须做这样的事情:

static void *at_exit_data = 0;

static void at_exit_handler(void)
{
    char *str = at_exit_data;
    printf("Exiting: %s\n", str);
}

int main(void);
{
    char buffer[] = "Message to be printed via functions registered with at_exit()";
    at_exit_data = buffer;
    at_exit(at_exit_handler);
    …processing…
    return 0;
}

同样,当程序从 main() 返回时,由 at_exit_data 指向的缓冲区已不存在,因此处理函数调用未定义的行为。

有一个相关的函数,at_quick_exit(),但是注册到它的函数只有在调用 quick_exit() 函数时才会被调用,这排除了在 main() 返回后调用的函数。