/bin
和/usr/bin
中的FreeBSD代码有一些修复,使用exit
而不是return
,这是什么意思?我所想到的是
return
语句可能会导致vfork(2)
破坏堆栈帧,这是唯一的原因吗?如果是这样,为什么只有部分命令在/bin
和/usr/bin
中得到了修复,而不是全部?根据5.1.2.2.3p1,如果主函数的返回类型与int兼容,则从主函数的初始调用返回相当于使用主函数返回的值作为参数调用exit函数。
这排除了你“破坏堆栈帧”的理论;对于符合C实现(至少对于非递归的main入口点),
return 0;
在功能上与exit(0);
相同。
我认为这个更改仅仅是风格上的,或者可能是由于无知而引导的。另一个可能性是作者想将main
转换为递归函数(或者main
已经被转换为递归函数;我只是快速地进行了一次grep,似乎不是立即递归)。最后,作为最后的可怕选择,也许FreeBSD使用的C实现是不符合标准的(我真的希望不是这样!)...
编辑:通过阅读这个答案,我突然想到这可能是绕过编译器错误的一个解决方法,但是很遗憾,我检查了使用atexit
的源代码,并没有找到进一步追究这条路线的理由。
让我们看看能否将其他人提供的各个链接融合成一个恰当的答案。
exit()
的调用会:
atexit()
注册的函数;tmpfile()
打开的文件;main
函数中的 return <n>
相当于 exit( <n> )
。
就C语言而言,main()
分配的内存如果没有被free()
释放,则会发生泄漏。Unix在进程终止时清理内存,但并非所有操作系统都这样做(!)。
显然,一些静态代码分析器认为在exit()
点仍然分配的内存未泄漏(而对于从main()
返回则是),这就是为什么进行了该提交(以消除警告)。
这是对代码分析器中的一个错误的解决方法。
当你从 main()
返回时,你离开了函数的作用域,这意味着局部对象被销毁。
由于 C++ 程序员享受确定性销毁的好处(与例如 Java 不同,即使在 VM 终止时也可能不执行您的析构函数...),他们倾向于使其析构函数做更多事情,而不仅仅是释放内存。网络连接、临时文件、被 ncurses
锁定的终端窗口,所有这些好处,C 程序员必须手动关心或使用 atexit()
。
当你从 main()
调用 std::exit()
函数时,该函数直接将控制权转移到运行时。 main()
函数永远不会返回,进程被终止而不调用 main()
的本地对象的析构函数。
std::exit()
被定义为刷新和关闭 C 输出流,但是对于 C++ 输出流却没有这样的规定。return
和exit()
从main()
的区别在于自动存储期内在main()
中定义的对象的生命周期(以及它们可能被在注册atexit()
的回调函数引用的罕见情况)。(2) 在exit()
之前分配内存但未释放的任何内存都将泄漏,main()
没有任何特殊之处。没有实际的静态分析器会混淆(作者也在做出假设并感到困惑)。(3) C++在这里是不相关的。 - Greg A. Woods在我看来,没有非常好的理由这样做。
这个特定的更改在freebsd邮件列表中的这篇文章中有讨论。
简而言之,提交日志应该是:
"在main()中使用exit()代替return以解决静态分析器的问题"
因此,这是为了帮助静态分析器和内存分析器,否则它们会将在main()中分配的内容报告为内存泄漏 - 大多数简单的实用程序不会打扰去释放它们,因为进程无论如何都将退出。
/bin/df
最近有一个提交,使用exit()
代替main()
中的return
,但/bin/cat
仍然在main()
中使用return
。 - oxnzmain()
中也要使用exit()
是良好风格和健壮实践。提交作者的推理和证明混乱而且过于复杂,并充满了未经证实的假设。return
和exit()
从main()
中的区别,在这个特定的例子中完全不相关。 - Greg A. Woods