我们应该在 C 中使用 exit() 吗?
Posted
技术标签:
【中文标题】我们应该在 C 中使用 exit() 吗?【英文标题】:Should we use exit() in C? 【发布时间】:2015-07-19 12:21:59 【问题描述】:question 关于在 C++ 中使用 exit
。答案讨论了这不是一个好主意,主要是因为 RAII,例如,如果在代码中的某处调用 exit
,则不会调用对象的析构函数,因此,例如,如果析构函数旨在将数据写入文件,则此不会发生,因为没有调用析构函数。
我对 C 中的这种情况很感兴趣。类似的问题也适用于 C 吗?我想既然在 C 中我们不使用构造函数/析构函数,C 中的情况可能会有所不同。那么在 C 中使用 exit
可以吗?例如,我见过有时在 C 中使用的以下函数:
void die(const char *message)
if(errno)
perror(message);
else
printf("ERROR: %s\n", message);
exit(1);
【问题讨论】:
“不会调用对象的析构函数”——这并不完全正确(参见:cplusplus.com/reference/cstdlib/exit)。您正在考虑 quick_exit(请参阅:cplusplus.com/reference/cstdlib/quick_exit/?kw=quick_exit)。 您可能还有一些操作系统特定的问题,例如SIGTERM
信号在 POSIX 和 Linux 上的 传统 角色.... 表现良好的服务器有望很好地处理它。你应该避免使用SIGKILL
(即尝试kill -TERM
然后kill -QUIT
,然后才使用kill -KILL
作为系统管理员)
在 Stack Overflow 推出近 7 年后,这怎么不是重复的?
@PeterMortensen:这是令人惊讶,但我不知道这个重复的好选择。高票数Use of exit()
function 并不密切(高票数令人惊讶——这不是一个好问题)。 Why should I not use exit() function in C? 如果有这个问题的答案,那将是一个很好的候选人。它没有;作为副本的反向关闭是合适的 - 我已经做到了。
我不确定你想在错误情况下使用'printf'。该函数使用缓冲区,但如果您有内存损坏,这些缓冲区可能不好。您可能只想使用 'write(2, "ERROR: ", 7);写(2,消息,strlen(消息));
【参考方案1】:
C 中的 exit()
函数被认为是“优雅的”退出,而不是 abort()
。
来自 C11 (N1570) 7.22.4.4/p2 退出函数(强调我的):
exit
函数导致正常程序终止发生。
该标准还在 7.22.4.4/p4 中指出:
接下来,所有具有未写入缓冲数据的打开流都被刷新,所有 打开的流被关闭,所有由
tmpfile
函数创建的文件 被删除。
7.21.3/p5 文件也值得一看:
如果
main
函数返回到其原始调用者,或者如果exit
函数被调用,所有打开的文件都被关闭(因此所有输出 流被刷新)在程序终止之前。其他途径 程序终止,例如调用abort
函数,不需要 正确关闭所有文件。
但是,如下面的 cmets 所述,您不能假设它会覆盖所有其他资源,因此您可能需要求助于atexit()
并为它们的发布单独定义回调。事实上,这正是 atexit()
打算做的事情,正如 7.22.4.2/p2 中所说的 atexit 函数:
atexit
函数将func
指向的函数注册为 在正常程序终止时不带参数调用。
值得注意的是,C 标准并没有准确说明已分配存储持续时间(即malloc()
)的对象应该发生什么,因此要求您了解在特定实现中它是如何完成的。对于现代的、面向主机的操作系统,系统可能会处理它,但您仍可能希望自己处理此问题,以使内存调试器(例如 Valgrind)静音。
【讨论】:
这在我看来是正确的答案。套接字连接在退出时关闭,因为结束的进程会关闭所有文件描述符,无论是否使用stdio
打开,即相当于 FD 上的 close()
。我不知道@BlueMoon 认为这在什么意义上是不正确的,但它不亚于对close()
的调用,如果需要进一步澄清,这正是atexit()
的用途。
@abligh 现代操作系统将关闭与该进程关联的所有 fd;但这是残酷而不是标准的(蓝月亮所说的)。
@black 如果需要进一步清理,可以使用atexit()
。使用exit()
本身并不比从main()
执行return
更残酷。
使用“良好实践”编写“正确”软件是为什么我有这么多应用程序在被要求时不会立即关闭的原因。:(如果操作系统可以做某事,你应该让它做它而不是用用户代码来尝试做一些已经存在的事情, 已经测试并已经调试. 如果您的软件系统无法承受突然的, 意外的关闭, (例如, 从某个线程调用立即进程内终止, 任务Manager 'End process', 'kill -9' or power failure),反正质量很差。
如果您可以在答案中添加 - 如果可能的话 - 在atexit
中可能需要释放什么样的资源,那将是很好的,否则没关系。 @BlueMoon:我不认为使用诸如die
之类的功能意味着不能编程,在某些情况下可能想要使用它。否则,实际上,您也可以仅使用返回值等来处理此问题。【参考方案2】:
是的,在 C 中使用 exit
是可以的。
为保证所有缓冲区和优雅有序关闭,建议使用此功能atexit
,更多信息请参阅此here
示例代码如下所示:
void cleanup(void)
/* example of closing file pointer and free up memory */
if (fp) fclose(fp);
if (ptr) free(ptr);
int main(int argc, char **argv)
/* ... */
atexit(cleanup);
/* ... */
return 0;
现在,每当调用exit
时,都会执行函数cleanup
,它可以进行正常关机、清理缓冲区、内存等。
【讨论】:
@IlmariKaronen 分配的内存不会以任何方式自动处理。 可能确保内存被回收回操作系统的是操作系统。 Grzegorz Szpetkowski 的论点反对您的说法:值得注意的是,C 标准并没有准确说明分配存储持续时间的对象(即 malloc())应该发生什么,因此要求您了解它在特定实现中是如何完成的。对于现代的、面向主机的操作系统,系统很可能会处理它,但您仍可能希望自己处理此问题,以使内存调试器(如 Valgrind)静音。【参考方案3】:您没有构造函数和析构函数,但您可以拥有资源(例如文件、流、套接字),正确关闭它们很重要。缓冲区无法同步写入,因此在未正确关闭资源的情况下退出程序可能会导致损坏。
【讨论】:
这个答案是“不要学习编程语言;学习如何编程”的一个很好的例子。选择不同的语言通常无法避免理解问题。 我认为这是不正确的。如果您调用exit()
(而不是_exit()
),则会调用atexit
例程并将stdio
缓冲刷新到磁盘。 exit()
正是为了让程序有序退出。
@abligh 例如,实时系统不会在 exit() 上释放 malloc 的内存,从而导致泄漏。 POSIX 共享内存是另一个例子。所以它取决于环境而不是严格遵循C标准。
@abligh:并非所有资源都是标准库提供的。虽然标准库做出某些保证肯定是对的,但您仍然需要考虑程序的全局控制流及其每个部分的职责,并确保每个待处理的职责是处理得当。术语“资源”是这个一般概念的简洁术语,超越了任何特定的库,处理资源最终是程序员的责任。像atexit
这样的设施可以提供帮助,尽管它们并不总是合适的。
@abligh:如果你愿意,你当然可以使用exit
,但这是一个全局控制的跳跃,伴随着我们非结构化控制的所有缺点'讨论过。作为一种从失败条件中终止的方法,它可能是合适的(这似乎是 OP 想要的),但对于正常的控制流程,我可能更喜欢设计具有适当退出条件的主循环,以便您确实结束从 main 返回。【参考方案4】:
使用exit()
就可以了
尚未提及的代码设计的两个主要方面是“线程”和“库”。
在单线程程序中,在您为实现该程序而编写的代码中,使用exit()
就可以了。当出现问题并且代码无法恢复时,我的程序会定期使用它。
但是……
但是,致电exit()
是无法撤消的单方面操作。这就是为什么“线程”和“库”都需要仔细考虑的原因。
线程程序
如果一个程序是多线程的,那么使用exit()
是一个戏剧性的动作,它会终止所有线程。退出整个程序可能是不合适的。退出线程,报告错误可能是合适的。如果你了解程序的设计,那么单方面退出或许是可以的,但一般来说是不能接受的。
库代码
“认识到程序设计”条款也适用于库中的代码。通用库函数调用exit()
很少是正确的。如果标准 C 库函数之一仅仅因为错误而无法返回,那么您将有理由感到不安。 (显然,exit()
、_Exit()
、quick_exit()
、abort()
等函数旨在不返回;这是不同的。)因此,C 库中的函数要么“不会失败”,要么以某种方式返回错误指示.如果您正在编写进入通用库的代码,则需要仔细考虑代码的错误处理策略。它应该符合预期使用的程序的错误处理策略,或者错误处理可以是可配置的。
我有一系列库函数(在一个带有标题"stderr.h"
的包中,这是一个如履薄冰的名字),它们旨在退出,因为它们用于错误报告。这些功能按设计退出。同一个包里还有一系列相关的函数,报错不退出。当然,现有功能是根据非现有功能实现的,但这是内部实现细节。
我还有很多其他的库函数,其中很多依赖"stderr.h"
代码来报告错误。这是我做出的设计决定,也是我可以接受的。但是,当退出的函数报告错误时,它会限制库代码的一般用途。如果代码调用了不退出的错误报告函数,那么函数中的主要代码路径必须妥善处理错误返回——检测它们并将错误指示传递给调用代码。
我的错误报告包的代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 stderr.c
和 stderr.h
的形式在 src/libsoq 子目录中找到。
【讨论】:
说的很好。您可能会看到here 一个库示例,当它通过malloc()
或realloc()
分配内存时调用abort()
,想象一下,您有一个应用程序,它与100 个库链接,您想知道哪一个和你的应用程序是如何崩溃的。更重要的是,我在他们的文档中没有发现任何提到 abort()
的内容(但不要误会我的意思。它是一个很棒的库)。
@GrzegorzSzpetkowski 它甚至不会在 fprinting 到 stderr 后手动调用 flush。哎哟!
@this:它不应该这样做。 stderr
的输出通常是行缓冲的。如果输出以换行符结束,系统无论如何都会刷新它。
@JonathanLeffler 依赖用户,不是明智之举。
@this: 不,依赖于遵循 C 标准要求的实现:§7.21.3 ¶7 在程序启动时,预定义了三个文本流,不需要显式打开— 标准输入(用于读取常规输入)、标准输出(用于写入常规输出)和标准错误(用于写入诊断输出)。最初打开时,标准错误流没有完全缓冲; ... 它需要程序员主动编码以使标准错误完全缓冲(如果程序员那么迟钝,对任何人都没有帮助——除了“不要使用代码”)。【参考方案5】:
在main()
以外的函数中避免使用exit
的一个原因是您的代码可能会脱离上下文。请记住,exit 是一种非本地控制流。像无法捕获的异常。
例如,您可能会编写一些存储管理函数,这些函数会在出现严重磁盘错误时退出。然后有人决定把它们搬进图书馆。从库中退出会导致调用程序以不一致的状态退出,它可能没有准备好。
或者您可以在嵌入式系统上运行它。没有地方可以退出到,整个事情在main()
中的while(1)
循环中运行。它甚至可能没有在标准库中定义。
【讨论】:
而且人们不应该被允许拥有厨刀,因为有人可能会抓起一把并试图玩弄它们,或者和他的孩子玩“抓”。显然,我不同意。如果有人决定将我的代码复制到他的程序中,而我的代码结果不适合他的目的,那就是他的问题,因为他没有阅读他正在吸收的代码。 一个相当粗俗的类比。 C 充满了可以 做但可能应该避免的事情。因此,IOCCC 的存在。请注意,我的帖子没有说“不应该”。【参考方案6】:根据您的操作,退出可能是退出 C 程序的最合乎逻辑的方式。我知道它对于检查以确保回调链正常工作非常有用。以我最近使用的这个回调示例为例:
unsigned char cbShowDataThenExit( unsigned char *data, unsigned short dataSz,unsigned char status)
printf("cbShowDataThenExit with status %X (dataSz %d)\n", status, dataSz);
printf("status:%d\n",status);
printArray(data,dataSz);
cleanUp();
exit(0);
在主循环中,我为这个系统设置了所有内容,然后在 while(1) 循环中等待。可以创建一个全局标志来退出 while 循环,但这很简单并且可以完成它需要做的事情。如果您正在处理任何打开的缓冲区,例如文件和设备,您应该在关闭之前清理它们以保持一致性。
【讨论】:
【参考方案7】:在大型项目中,除了 coredump 之外的任何代码都可以退出,这很糟糕。 Trace 对维护在线服务器非常重要。
【讨论】:
以上是关于我们应该在 C 中使用 exit() 吗?的主要内容,如果未能解决你的问题,请参考以下文章