如何结束C ++代码


267

如果希望满足特定条件,我希望C ++代码停止运行,但是我不确定该怎么做。因此,只要if语句为真,在任何时候都将终止代码,如下所示:

if (x==1)
{
    kill code;
}

98
使用正确的逻辑。在main()使用返回中,在函数中使用适当的返回值或引发适当的Exception。不要使用exit()
凯-SE很邪恶

6
@JonathanLeffler:使用define编译时NDEBUGassert很可能会变成无操作,因此您不应仅依靠它来检测错误。
MvG 2015年

13
@jamesqf:一个returnfrom main()是完全合理的。它将程序报告的退出代码设置到操作系统,这在许多情况下很有用(例如,如果您的程序可以用作较大过程的一部分)。
AAT 2015年

11
有趣的是,这个问题与5年前的问题重复,其接受的答案却大不相同:stackoverflow.com/questions/1116493/how-to-quit-ac-program我想这个社区花了5年的时间才盲目学习调用std :: exit可能不好?
布兰丁

5
问题为什么使用exit()被认为是不好的?– SO 25141737如何退出C ++程序?–现在关闭SO 1116493,作为此副本的副本。其中第一个提到的“仅崩溃软件”可能值得一看,如果只是激发您对如何编写健壮软件的思考。
Jonathan Leffler

Answers:


431

有几种方法,但是首先您需要了解为什么对象清理很重要,因此std::exit在C ++程序员中将其边缘化。

RAII和堆栈展开

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

如果没有到达析构函数刷新并关闭文件,该怎么办?谁知道!但是可能不会将原本应该写入的所有数据写入文件中。

例如考虑这段代码

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

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

void mad()
{
    auto ptr = std::make_unique<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);
}

每种可能性发生的事情是:

  • 可能性1: Return本质上离开了当前函数范围,因此它知道生命周期的结束,os因此将调用其析构函数并通过关闭文件并将其刷新到磁盘来进行适当的清理。
  • 情况2:引发异常还可以照顾当前范围内对象的生命周期,从而进行适当的清理...
  • 可能性3:在这里展开堆栈就起作用了!即使抛出异常inner_mad,展开器也会经过的堆栈madmain执行适当的清理,所有对象(包括ptr和)都将被正确地销毁os
  • 情形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-规范不允许该异常(15.4),或者[...]

    [...]

  2. 在这种情况下,std :: terminate()被称为(18.8.3)。在没有找到匹配处理程序的情况下,由实现定义是否在调用std :: terminate()之前取消堆栈的堆栈 [...]

所以我们必须抓住它!

一定要抛出异常并抓住它!

由于未捕获的异常可能不会执行堆栈展开(因此将无法执行适当的清理),因此我们应在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;
    }
    /* ... */
}

[不要] 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

25
这非常有用:值得注意的是,我从未意识到抛出在任何地方都无法处理的异常(例如:某些new抛出std::bad_alloc并且您的程序忘记了在任何地方捕获该异常)都不会在终止之前适当地释放堆栈。这傻似乎对我说:这本来是很容易基本上包裹呼吁main在一个平凡的try{- }catch(...){}块,以确保栈展开在这种情况下,处理得当,在没有成本(我指的是:程序不使用,这将支付不罚款)。是否有任何特定原因未完成?
马克·范·吕文

12
@MarcvanLeeuwen的一个可能原因是调试:您想在引发未处理的异常后立即闯入调试器。展开堆栈并进行清理将清除导致您要调试的崩溃的原因的上下文。如果没有调试器,则最好转储内核以便可以进行事后分析。
休·艾伦

11
有时我的浏览器会出错(我归咎于闪存),并消耗了数GB的RAM,并且我的操作系统将页面转储到硬盘驱动器上,从而使所有操作变慢。当我关闭浏览器时,它会进行适当的堆栈展开,这意味着所有这些GB的RAM都将从硬盘驱动器中读取并复制到内存中以进行释放,这需要一分钟左右的时间。我希望它们能std::abort代替他们使用,以便操作系统可以释放所有内存,套接字和文件描述符,而无需一分钟时间。
nwp 2015年

2
@nwp,我了解这种感觉。但是立即杀死可能会损坏文件,无法保存我最近的标签,等等:)
Paul Draper 2015年

2
@PaulDraper如果浏览器无法恢复掉电前打开的选项卡,我当然不会认为这是可以容忍的。当然,如果我刚刚打开一个标签,并且还没有时间保存它,它将丢失。但是除此之外,我说失去它没有任何借口。
卡巴斯德(Kasperd)

61

正如马丁·约克(Martin York)所述,退出不像返回那样执行必要的清理。

最好使用return代替出口。如果您不在主程序中,则无论您要退出该程序,请先返回主程序。

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

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

不仅如此,在程序中具有多个退出点会使调试更加困难。仅在可以辩解的情况下使用出口。


2
您建议如何在更大的程序中实现此行为?如果在代码中更深的某个地方触发了错误情况并应退出程序,您如何始终干净地返回main?
Janusz,2009年

5
@Janusz,在这种情况下,可以使用/抛出异常,如果不返回预定义的值,即具有该函数的返回值,例如成功时返回0,失败时返回1,但是继续执行,如果失败则返回-1并退出程序。根据函数的返回值,如果失败,则在执行更多清理活动后才从主函数返回。最后,明智地使用出口,我并不是要避免这种情况。
Narendra N

1
@NarendraN,“必要的清理”是模糊的-操作系统将注意(Windows / Linux)正确释放内存和文件句柄。至于“失踪”的文件输出:如果你坚持认为这可能是一个真正的问题,请参见stackoverflow.com/questions/14105650/how-does-stdflush-work 如果你有一个错误情况,适当的记录告诉你,你的程序达到未定义状态,您可以在记录点之前设置断点。这如何使调试更加困难?
马库斯

2
请注意,此答案已与“ 如何退出C ++程序 ”问题合并– SO 1116493。在提出此问题之前大约六年,就写了这个答案。
乔纳森·莱夫勒

对于现代C ++中使用 return (EXIT_SUCCESS);,而不是return(0)
乔纳斯斯坦

39

调用该std::exit函数。   


2
调用该函数时会调用哪些对象的析构函数?
罗布·肯尼迪

37
exit()不返回。因此,无法进行堆栈展开。甚至全局对象也不会被破坏。但是将调用在atexit()中注册的函数。
马丁·约克

16
exit()当您的库代码在我的宿主进程中运行时,请不要调用-后者将在任何地方退出。
Sharptooth

5
请注意,此答案已与“ 如何退出C ++程序 ”问题合并– SO 1116493。在提出此问题之前大约六年,就写了这个答案。
乔纳森·莱夫勒

23

人们说“呼叫退出(返回码)”,但这是错误的形式。在小型程序中,这很好,但是与此有关的还有很多问题:

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

确实,您唯一应该退出问题的地方就是main.cpp中的以下行:

return 0;

如果要使用exit()处理错误,则应了解异常(和嵌套异常),这是一种更为优雅,安全的方法。


9
在多线程环境中,通过main()不会处理在其他线程中引发的异常-在从属线程到期之前,需要一些手动的跨线程通信。
史蒂夫·吉勒姆

3
1.和2.由程序员决定,使用正确的日志记录就不会有问题,因为执行永远停止。至于3:这是完全错误的,操作系统将释放内存-可能不包括嵌入式设备/实时,但如果这样做,您可能知道您的东西。
马库斯

1
请注意,此答案已与“ 如何退出C ++程序 ”问题合并– SO 1116493。在提出此问题之前大约六年,就写了这个答案。
乔纳森·莱夫勒

1
但是,如果发生错误,则不应返回0。应返回1(或可能返回其他值,但是1始终是一个安全的选择)。
Kef Schecter

14

return 0;将其放在您想要的位置int main(),该程序将立即关闭。


夜客@ evan-carslake和其他所有人,我还要补充一点,这个SO 问题#36707讨论了return语句是否应该只出现在例程中的任何地方。这是一个解决方案;但是,根据具体情况,我不会说这是最好的解决方案。
2015年

1
OP对此一无所获,所以我认为假定代码应在函数内部是相当轻率的main
Marc van Leeuwen 2015年

@localhost在任何情况下,在任何情况下,return语句都是完全可选的。但是,int main()是不同的。该程序用于int main()开始和结束。没有其他方法可以做到这一点。该程序将一直运行,直到您返回true或false。几乎所有情况下,您都将返回1或true,以指示程序已正确关闭(例如:如果您无法释放内存或任何原因,则可能使用false。)让程序本身运行完毕始终是一个坏主意,例如:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
埃文·卡斯雷克

@EvanCarslake理解,如果您注意到我在其他地方针对此问题发表的评论,我对此很熟悉int main;但是,在主例程中具有多个return语句并不总是一个好主意。潜在的警告之一是提高代码的可读性。但是,请使用类似布尔标志的方法来更改状态,以防止某些代码段的代码在该应用程序状态下运行。我个人认为,常见的return语句使大多数应用程序更具可读性。最后,关于内存清理和在退出前正确关闭I / O对象的问题。
2015年

2
@EvanCarslake你不回truemain指示正确的终止。您必须返回零或EXIT_SUCCESS(或,如果需要,false则将其隐式转换为零)以指示正常终止。为了表明失败,您可以返回EXIT_FAILURE。其他任何代码含义都是实现定义的(在POSIX系统上,这意味着实际的错误代码)。
罗斯兰

11

当执行流到达主函数的末尾时,程序将终止。

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


2
请注意,此答案已与“ 如何退出C ++程序 ”问题合并– SO 1116493。在提出此问题之前大约六年,就写了这个答案。
Jonathan Leffler



9

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

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


1
使用出口可能意味着您有设计问题。如果程序正确运行,则应在main时终止return 0;。我认为exit()assert(false);等,应在开发仅用于及早发现问题。
CoffeDeveloper 2015年

2
请注意,此答案已与“ 如何退出C ++程序 ”问题合并– SO 1116493。在提出此问题之前大约六年,就写了这个答案。
Jonathan Leffler

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);
    }
}

将secondary_main放置在其中的所有原始内容中,即原始main重命名为secondary_main,并添加了上面的存根main。这只是一个很好的选择,因此在托盘和主容器之间没有太多代码。

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

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

总体而言,C错误处理-退出和信号-和C ++错误处理-尝试/捕获/抛出异常-最多不能同时发挥作用。

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

throw "error message"

或更具体的异常类型。


顺便说一句:我很清楚,在可能与动态内存分配有关的异常处理程序中或周围,使用像字符串之类的数据类型可能涉及动态内存分配,不是一个好主意。C样式的字符串常量不是问题。
Krazy Glew 2014年

1
调用exit程序毫无意义。既然进入main,就可以return exitCode;
罗斯兰

-1

如果您的if语句处于循环中,则可以使用

 break; 

如果要转义一些代码并继续循环,请使用:

继续;

如果您的if语句不在循环中,则可以使用:

 return 0;

Or 




  exit();

-2

Dude ... exit()函数在stdlib.h下定义

因此,您需要添加一个预处理器。

放在include stdlib.h标题部分

然后exit();在您喜欢的任何地方使用,但要记住在出口的括号中放置一个整数。

例如:

exit(0);

-2

如果要测试的条件确实是个坏消息,请执行以下操作:

*(int*) NULL= 0;

这给了我一个不错的coredump,可以从那里检查情况。


4
最好将其优化,因为它会导致未定义的行为。实际上,具有这样一条语句的整个执行分支可以由编译器清除。
Ruslan

-2

要破坏条件,请使用return(0);

因此,在您的情况下,它将是:

    if(x==1)
    {
        return 0;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.