如何结束C++代码

The*_*man 252 c++

如果满足某个条件,我希望我的C++代码停止运行,但我不知道该怎么做.因此,只要if声明为真,就可以在任何时候终止代码,如下所示:

if (x==1)
{
    kill code;
}
Run Code Online (Sandbox Code Playgroud)

Den*_*rim 417

有几种方法,但首先你需要理解为什么对象清理很重要,因此std::exit在C++程序员中边缘化的原因.

RAII和堆栈展开

C++使用了一个名为RAII的习语,简单来说就是对象应该在构造函数中执行初始化并在析构函数中进行清理.例如,std::ofstream类[可]在构造函数中打开文件,然后用户对其执行输出操作,最后在其生命周期结束时(通常由其作用域确定),调用析构函数,实质上关闭文件并刷新任何书面内容进入磁盘.

如果你没有到析构函数刷新并关闭文件会发生什么?谁知道!但可能它不会写入它应该写入文件的所有数据.

例如,考虑这段代码

#include <fstream>
#include <exception>
#include <memory>

void inner_mad()
{
    throw std::exception();
}

void mad()
{
    std::unique_ptr<int> ptr(new int);
    inner_mad();
}

int main()
{
    std::ofstream os("file.txt");
    os << "Content!!!";

    int possibility = /* either 1, 2, 3 or 4 */;

    if(possibility == 1)
        return 0;
    else if(possibility == 2)
        throw std::exception();
    else if(possibility == 3)
        mad();
    else if(possibility == 4)
        exit(0);
}
Run Code Online (Sandbox Code Playgroud)

每种可能性都会发生什么:

  • 可能性1:返回基本上离开了当前的函数范围,因此它知道生命周期的结束os因此调用它的析构函数并通过关闭并将文件刷新到磁盘来进行适当的清理.
  • 可能性2:抛出异常也会处理当前范围内对象的生命周期,从而进行适当的清理......
  • 可能性3:这里堆叠展开进入行动!即使抛出异常inner_mad,展开器也将通过堆栈madmain进行适当的清理,所有对象都将被正确破坏,包括ptros.
  • 可能性4:嗯,这里?exit是一个C函数,它不知道也不兼容C++习语.它不会对您的对象执行清理,包括os在相同的范围内.因此,您的文件将无法正常关闭,因此内容可能永远不会写入其中!
  • 其他可能性:它只是通过执行隐式return 0而离开主范围,因此具有与可能性1相同的效果,即适当的清理.

但是不要对我刚才告诉你的事情这么肯定(主要是可能性2和3); 继续阅读,我们将找到如何执行适当的异常清理.

可能的方法结束

从主要回来!

你应该尽可能地这样做; 总是希望通过从main返回正确的退出状态来从程序返回.

您的程序的调用者,可能还有操作系统,可能想知道您的程序应该执行的操作是否成功.出于同样的原因,您应该返回零或EXIT_SUCCESS表示程序成功终止并EXIT_FAILURE发出程序终止失败的信号,任何其他形式的返回值都是实现定义的(第18.5/8节).

但是你可能在调用堆栈中非常深,并且返回所有这些可能会很痛苦......

[不]抛出异常

抛出异常将使用堆栈展开执行正确的对象清理,方法是调用任何先前作用域中每个对象的析构函数.

但这是抓住了!它是实现 - 定义是否在未处理抛出异常时(通过catch(...)子句)执行堆栈展开,或者即使noexcept在调用堆栈中间有函数也是如此.这在§15.5.1[except.terminate]中说明:

  1. 在某些情况下,必须放弃异常处理以获得不太精细的错误处理技术.[注意:这些情况是:

    [...]

    - 当异常处理机制找不到抛出异常的处理程序时(15.3),或者当搜索处理程序(15.3)遇到函数的最外层块时,noexcept-specification不允许异常(15.4),或者[...]

    [...]

  2. 在这种情况下,调用std :: terminate()(18.8.3).在没有找到匹配处理程序的情况下,无论是否在调用std :: terminate()之前展开堆栈,它都是实现定义的 [...]

所以我们必须抓住它!

抛出一个异常并在main处抓住它!

由于未捕获的异常可能不执行堆栈展开(因此不会执行正确的清理),我们应该在main中捕获异常然后返回退出状态(EXIT_SUCCESSEXIT_FAILURE).

所以一个可能很好的设置将是:

int main()
{
    /* ... */
    try
    {
        // Insert code that will return by throwing a exception.
    }
    catch(const std::exception&)  // Consider using a custom exception type for intentional
    {                             // throws. A good idea might be a `return_exception`.
        return EXIT_FAILURE;
    }
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

[不] std :: exit

这不执行任何类型的堆栈展开,并且堆栈上没有活动对象将调用其各自的析构函数来执行清理.

这在§3.6.1/ 4 [basic.start.init]中强制执行:

在不离开当前块的情况下终止程序(例如,通过调用函数std :: exit(int)(18.5))不会销毁具有自动存储持续时间的任何对象(12.4).如果在销毁具有静态或线程存储持续时间的对象期间调用std :: exit来结束程序,则程序具有未定义的行为.

现在想一想,你为什么要做这样的事情?你痛苦地损坏了多少件物品?

其他[作为坏]替代品

还有其他方法可以终止程序(崩溃除外),但不推荐使用它们.只是为了澄清它们将在这里展示.注意正常的程序终止 并不意味着堆栈展开,而是指操作系统的正常状态.

  • std::_Exit 导致正常的程序终止,就是这样.
  • std::quick_exit导致正常的程序终止并调用std::at_quick_exit处理程序,不执行其他清理.
  • std::exit导致正常的程序终止,然后调用std::atexit处理程序.执行其他类型的清理,例如调用静态对象析构函数.
  • std::abort导致程序异常终止,不执行清理.如果程序以非常非常意外的方式终止,则应调用此方法.除了向操作系统发出异常终止信号外,它什么都不做.在这种情况下,某些系统会执行核心转储.
  • std::terminate默认情况下调用std::terminate_handler哪些调用std::abort.

  • 这是非常有用的信息:值得注意的是,我从未意识到抛出一个未在任何地方处理的异常(例如:某些`new`抛出`std :: bad_alloc`并且你的程序忘记在任何地方捕获该异常)将****不正确在终止之前展开堆栈.这对我来说似乎很愚蠢:在一个简单的`try {`-`} catch(...){}`块中实际上将调用封装到`main`很容易,这样可以确保在这样的情况下正确完成堆栈展开案件,不收取任何费用(我的意思是:不使用此项目的程序将不会受到任何处罚).有没有特别的原因没有这样做? (23认同)
  • @MarcvanLeeuwen一个可能的原因是调试:你想在抛出一个未处理的异常后立即进入调试器.展开堆栈并进行清理会清除导致您要调试的崩溃的上下文.如果没有调试器,最好转储核心,以便可以完成事后分析. (12认同)
  • 有时我的浏览器会出错(我责怪闪存)并消耗几千兆字节的内存,而我的操作系统会将硬盘上的页面转储,这会使一切变慢.当我关闭浏览器时,它会进行正确的堆栈展开,这意味着所有这些千兆字节的RAM都从硬盘驱动器中读取并复制到内存中以便释放,这需要一分钟左右.我希望他们使用`std :: abort`代替,这样操作系统就可以释放所有内存,套接字和文件描述符而无需交换一分钟. (10认同)
  • @nwp,我理解这种感觉.但立即杀死可能会损坏文件,而不是保存我最近的标签等:) (2认同)
  • @PaulDraper如果浏览器无法恢复断电之前打开的标签,我肯定不会认为这是可以容忍的.当然,如果我刚刚打开一个标签,并且还没有时间保存它,它就会丢失.但除此之外,我说没有理由失去它. (2认同)

Nar*_*a N 61

正如马丁约克所提到的那样,退出并不像返回那样进行必要的清理.

在出口处使用返回总是更好.如果您不在主要位置,无论您想要退出该计划,请先返回主要位置.

考虑下面的例子.使用以下程序,将使用提及的内容创建文件.但是如果返回被注释并且未注释exit(0),则编译器不会向您保证该文件将具有所需的文本.

int main()
{
    ofstream os("out.txt");
    os << "Hello, Can you see me!\n";
    return(0);
    //exit(0);
}
Run Code Online (Sandbox Code Playgroud)

不仅如此,在程序中拥有多个退出点将使调试更加困难.只有在可以对齐时才使用exit.

  • @Janusz,在这种情况下,你可以使用/ throw异常,如果没有返回一个预定义的值,即从函数返回值,例如成功时返回0,失败时返回1,但继续执行,-1表示失败并退出程序.根据函数的返回值,如果失败,只需在执行更多清理活动后从主函数返回.最后,明智地使用退出,我并不是要避免它. (5认同)
  • 你有什么建议在一个更大的程序中实现这种行为?如果在代码中更深处的某个地方触发了应该退出程序的错误条件,那么如何总是干净利落地返回main? (2认同)
  • 请注意,此答案是从问题 [如何退出 C++ 程序?– SO 1116493](http://stackoverflow.com/questions/1116493)。这个答案是在提出这个问题之前大约 6 年写的。 (2认同)

Otá*_*cio 37

调用该std::exit函数.   

  • exit()不会返回.因此,不会发生堆栈展开.即使是全局对象也不会被破坏.但是将调用使用atexit()注册的函数. (37认同)
  • 当您的库代码在我的主机进程中运行时,请不要调用`exit()` - 后者将在不知名的地方退出. (15认同)
  • 请注意,这个答案是从[如何退出C++程序? - SO 1116493](http://stackoverflow.com/questions/1116493).这个答案是在提出这个问题之前大约6年写的. (4认同)
  • 调用该函数时会调用哪些对象的析构函数? (2认同)

jke*_*eys 23

人们说"呼叫退出(返回代码)",但这是不好的形式.在小程序中它很好,但是有很多问题:

  1. 您将最终从该程序中获得多个退出点
  2. 它使代码更复杂(比如使用goto)
  3. 它无法释放在运行时分配的内存

真的,唯一一次你应该退出问题的是main.cpp中的这一行:

return 0;
Run Code Online (Sandbox Code Playgroud)

如果您使用exit()来处理错误,您应该了解异常(和嵌套异常),作为一种更优雅和安全的方法.

  • 在多线程环境中,不会通过main()处理在不同线程中抛出的异常 - 在从属线程到期之前需要一些手动跨线程通信. (9认同)
  • 1.和2.取决于程序员,通过适当的记录,这不是问题,因为执行永久停止.至于3:这是完全错误的,操作系统将释放内存 - 可能排除嵌入式设备/实时,但如果你这样做,你可能知道你的东西. (2认同)

Eva*_*ake 14

return 0;把它放在你想要的任何地方int main(),程序将立即关闭.

  • @EvanCarslake你不从'main`返回`true`来表示正确的终止.您必须返回零或`EXIT_SUCCESS`(或者,如果您需要,`false`,它将被隐式转换为零)以指示正常终止.要指示失败,您可以返回"EXIT_FAILURE".任何其他代码含义是实现定义的(在POSIX系统上它将意味着实际的错误代码). (2认同)

Goz*_*Goz 11

从您的返回值main或使用该exit函数.两者都采取int.除非您有外部流程监视返回值,否则返回的值并不重要.

  • 请注意,这个答案是从[如何退出C++程序? - SO 1116493](http://stackoverflow.com/questions/1116493).这个答案是在提出这个问题之前大约6年写的. (3认同)

chr*_*ney 11

程序将在执行流程到达主函数结束时终止.

要在此之前终止它,可以使用exit(int status)函数,其中status是返回到启动程序的任何值的值.0通常表示非错误状态

  • 请注意,这个答案是从[如何退出C++程序? - SO 1116493](http://stackoverflow.com/questions/1116493).这个答案是在提出这个问题之前大约6年写的. (2认同)

Jag*_*ath 11

如果代码中某处有错误,则抛出异常或设置错误代码.抛出异常而不是设置错误代码总是更好.

  • 请注意,这个答案是从[如何退出C++程序? - SO 1116493](http://stackoverflow.com/questions/1116493).这个答案是在提出这个问题之前大约6年写的. (2认同)

Bri*_*new 9

通常,您将使用exit()具有适当退出状态的方法.

零意味着成功运行.非零状态表示发生了某种问题.父进程(例如shell脚本)使用此退出代码来确定进程是否已成功运行.

  • 请注意,这个答案是从[如何退出C++程序? - SO 1116493](http://stackoverflow.com/questions/1116493).这个答案是在提出这个问题之前大约6年写的. (2认同)

Kra*_*lew 7

除了调用exit(error_code) - 它调用atexit处理程序,而不调用RAII析构函数等 - 越来越多我使用异常.

越来越多我的主程序看起来像

int main(int argc, char** argv) 
{
    try {
        exit( secondary_main(argc, argv );
    }
    catch(...) {
        // optionally, print something like "unexpected or unknown exception caught by main"
        exit(1);
    }
}
Run Code Online (Sandbox Code Playgroud)

其中secondary_main所在的所有东西都放在哪里 - 即原始main重命名为secondary_main,并添加上面的stub main.这只是一个非常好的,所以托盘和主要的捕获之间没有太多的代码.

如果需要,请捕获其他异常类型.
我非常喜欢捕获字符串错误类型,比如std :: string或char*,并在main中的catch处理程序中打印它们.

使用这样的异常至少允许调用RAII析构函数,以便它们可以进行清理.这可能是愉快和有用的.

总的来说,C错误处理 - 退出和信号 - 以及C++错误处理 - 尝试/捕获/抛出异常 - 最好不一致地一起玩.

然后,您在哪里检测到错误

throw "error message"
Run Code Online (Sandbox Code Playgroud)

或一些更具体的异常类型.

  • 在你的程序中调用 `exit` 是没有意义的。因为你在 `main` 中,你可以只用 `return exitCode;`。 (2认同)