从 C 程序中的任何位置调用时,C 编程中的 return 和 exit 语句有什么区别?
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() 系列的函数永远不会返回给调用者的理由。
我写了两个程序:
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() 。
在 C 中,在程序的启动函数(可以是 main()
、wmain()
、_tmain()
或编译器使用的默认名称)中使用时没有太大区别。
如果您在 main()
中使用 return
,则控制权会返回到最初启动您的程序的 C 库中的 _start()
函数,然后无论如何都会调用 exit()
。因此,您使用哪一个并不重要。
return 语句退出当前函数,exit() 退出程序
they are the same when used in main() function
return 也是一个语句,而 exit() 是一个需要 stdlb.h 头文件的函数
在大多数情况下,在 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()
返回后调用的函数。
main()
中使用return
。当然,我在main()
的末尾使用return 0;
— 我有时在函数体中使用exit();
。我不喜欢 C99 规则关于从main()
的末尾掉下来等同于return 0;
的规则;这是一个愚蠢的特殊情况(尽管 C++ 率先造成损害)。at_quick_exit()
、_Exit()
(也在 C99 中)和quick_exit()
。_exit()
函数来自 POSIX,但本质上与_Exit()
相同。