我们应该在 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.cstderr.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() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的exit函数

php - 我应该在调用 Location: 标头后调用 exit() 吗?

我们应该在子进程中使用退出还是返回

exit()和_exit()

一个函数中 有return后有必要用exit吗

c中的exit()函数的作用,是退出程序还是跳出函数?