在C程序中从任何地方调用时,return语句和exit语句有什么区别?
for
或break
这样的语言关键字。唯一的情况是在main()
函数中,当从主函数返回时执行exit()
(几乎)相同的操作。
在大多数C实现中,main
是由某些启动代码调用的真正的函数,该代码执行类似于int ret = main(argc, argv); exit(ret);
的操作。 C标准保证如果main
返回,则发生与此等效的操作,但是实现方式不同。
使用return
的示例:
#include <stdio.h>
void f(){
printf("Executing f\n");
return;
}
int main(){
f();
printf("Back from f\n");
}
如果你运行这个程序,它会打印出:
Executing f Back from f
exit() 的另一个例子:
#include <stdio.h>
#include <stdlib.h>
void f(){
printf("Executing f\n");
exit(0);
}
int main(){
f();
printf("Back from f\n");
}
Executing f
你永远不会从"f"返回。另请注意,调用库函数exit()
需要包含#include <stdlib.h>
。
还要注意exit()
的参数是整数(它是启动器进程可以获取的进程返回状态;惯用法是成功为0或任何其他值为错误)。
返回语句的参数是函数返回类型。如果函数返回void,则可以在函数末尾省略return。
最后一个要点,exit()
有两种形式_exit()
和exit()
。两种形式之间的区别在于exit()
(和从主函数返回)在真正终止进程之前调用使用atexit()
或on_exit()
注册的函数,而_exit()
(来自#include <unistd.h>
或其同义词_Exit来自#include <stdlib.h>
)立即终止进程。
现在还有一些特定于C ++的问题。
当 C++ 退出函数(通过 return
)时,它比 C 做的工作要多得多。具体而言,它会调用超出范围的局部对象的析构函数。在大多数情况下,程序员不太关心进程停止后程序的状态,因此这并不会有太大的区别:分配的内存将被释放,文件资源将关闭等等。但是如果您的析构函数执行输入/输出操作,则可能会很重要。例如,自动创建的 C++ OStream
在调用 exit 时不会被刷新,您可能会丢失一些未刷新的数据(另一方面,静态的 OStream
将被刷新)。
如果您使用老旧的 C FILE*
流,则不会发生这种情况。这些将在 exit()
上刷新。实际上,规则对于注册的退出函数也是相同的,在所有正常终止中 FILE*
都会被刷新,其中包括 exit()
,但不包括对 _exit()
或 abort() 的调用。
您还应该记住,C++ 提供了第三种退出函数的方式:抛出异常。这种退出函数的方式将会调用析构函数。如果它在调用者链中没有任何地方被捕获,异常可以上升到 main() 函数并终止进程。
如果您从程序中的任何位置调用exit()
或从main()
中使用return
,则将调用全局(静态)C++对象的析构函数。如果使用_exit()
或abort()
终止程序,则不会调用它们。 abort()
通常在调试模式下非常有用,目的是立即停止程序并获取堆栈跟踪(进行事后分析)。它通常隐藏在assert()
宏后面,仅在调试模式下活动。
exit()何时有用?
exit()
意味着您想立即停止当前进程。当我们遇到某种无法恢复的问题时,它可以用于错误管理,这将不再允许您的代码执行任何有用的操作。当控制流复杂且必须向上传播错误代码时,它通常很方便。但请注意,这是糟糕的编码实践。在大多数情况下,默默地结束进程是最糟糕的行为,应该优先考虑实际的错误管理(或在C ++中使用异常)。
如果在库中直接调用exit()
,特别是不好的,因为它会使库用户无法使用,并且库用户应该选择是否实现某种错误恢复。如果您想要一个调用exit()
的库的示例是不好的,它会导致人们问this question。
对于支持fork()的操作系统来说,使用exit()
是结束由它启动的子进程的不容置疑的合法方法。回到fork()之前的代码通常不是一个好主意。这就是解释为什么exec()系列函数永远不会返回给调用者的原因。
main()
函数中使用return
语句。当然,在main()
函数的末尾我会使用return 0;
语句——有时我也会在函数体内使用exit();
语句。我不喜欢C99规则关于从main()
函数结尾掉出被视为等同于return 0;
的特殊情况;这是一个很荒谬的规定(虽然C++更早地引领了这种做法)。 - Jonathan Lefflerat_quick_exit()
、_Exit()
(也在C99中),以及quick_exit()
。 _exit()
函数来自POSIX,但本质上与_Exit()
相同。 - Jonathan Leffler我写了两个程序:
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在大多数情况下,在C程序中使用return
和调用exit()
来终止main()
没有任何区别。
当有所不同的情况是,如果您创建的代码将在从main()
返回后执行,并且依赖于main()
中的局部变量。这种情况会通过setvbuf()
等方式显现:
int main(void)
{
char buffer[BUFSIZ];
setvbuf(stdout, buffer, _IOFBF, BUFSIZ);
…code using stdout…
return 0;
}
setvbuf()
提供的缓冲区会在main()
返回时超出作用域,但是刷新和关闭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;
}
at_exit_data
指向的缓冲区在程序从main()
返回后已经停止存在,因此处理程序函数会引发未定义的行为。
有一个相关的函数at_quick_exit()
,但是只有在调用quick_exit()
函数时才会调用注册的函数,这排除了在main()
返回后调用这些函数的可能性。
they are the same when used in main() function