【问题标题】:How to end C++ code如何结束 C++ 代码
【发布时间】:2010-11-10 03:08:42
【问题描述】:

如果满足某个条件,我希望我的 C++ 代码停止运行,但我不知道该怎么做。因此,如果if 语句为真,则在任何时候都可以像这样终止代码:

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

【问题讨论】:

  • 使用正确的逻辑。在main() 中使用return,在函数中使用适当的返回值或抛出适当的异常。 不要使用exit()!
  • @JonathanLeffler:在定义 NDEBUG 的情况下编译时,assert 可能会变成空操作,因此您不应该依赖它来检测错误。
  • @jamesqf:来自main()return 是完全合乎逻辑的。它设置程序向操作系统报告的退出代码,这在许多情况下都很有用(例如,如果您的程序可以用作更大进程的一部分)。
  • 有趣的是,这个 Q 是 5 年前这个 Q 的副本,其接受的答案完全不同:stackoverflow.com/questions/1116493/how-to-quit-a-c-program 我想社区花了 5 年时间才知道盲目调用 std::exit 可能不好吗?
  • 问题Why is using exit() considered bad? – SO 25141737How to quit a C++ program? – SO 1116493 现在作为这个问题的副本关闭。其中第一个提到了“仅崩溃软件”,如果只是为了激发您对如何编写健壮软件的思考,可能值得一看。

标签: c++


【解决方案1】:

有几种方法,但首先您需要了解为什么对象清理很重要,以及 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 的堆栈进行适当的清理,所有对象都将被正确销毁,包括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;
    }
    /* ... */
}

[不要] std::exit

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

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

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

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

其他 [as bad] 替代品

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

  • 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 并且您的程序忘记在任何地方捕获该异常)会没有在终止前正确展开堆栈。这对我来说似乎很愚蠢:本质上很容易将对main 的调用封装在一个琐碎的try{-}catch(...){} 块中,这将确保在这种情况下正确地完成堆栈展开,而无需任何成本(通过它我意思是:不使用它的程序不会受到惩罚)。没有这样做有什么特别的原因吗?
  • @MarcvanLeeuwen 一个可能的原因是调试:一旦抛出未处理的异常,您就想闯入调试器。展开堆栈并进行清理将删除导致您要调试的崩溃的上下文。如果没有调试器,最好转储内核以便进行事后分析。
  • 有时我的浏览器出现错误(我怪罪闪存)并消耗了几 GB 的 RAM,并且我的操作系统将页面转储到硬盘驱动器上,使一切变慢。当我关闭浏览器时,它会正确展开堆栈,这意味着从硬盘驱动器读取所有这些千兆字节的 RAM 并将其复制到内存中以便释放,这需要一分钟左右的时间。我真希望他们改用std::abort,这样操作系统就可以释放所有内存、套接字和文件描述符而无需一分钟交换。
  • @nwp,我理解这种感觉。但是立即杀死可能会损坏文件,不保存我最近的标签等:)
  • @PaulDraper 如果浏览器无法恢复我在断电前打开的标签,我当然不会认为这是可以容忍的。当然,如果我刚刚打开一个标签,而它还没有来得及保存它,它就会丢失。但除此之外,我说没有理由失去它。
【解决方案2】:

正如 Martin York 所说,exit 不像 return 那样执行必要的清理。

使用 return 代替 exit 总是更好。 万一你不在main中,无论你想退出程序的哪个地方,先回到main。

考虑下面的例子。 使用以下程序,将使用提到的内容创建一个文件。 但是如果 return 被注释和未注释 exit(0),编译器不能保证文件将具有所需的文本。

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

不仅如此,程序中有多个退出点会使调试更加困难。 只有在有正当理由时才使用退出。

【讨论】:

  • 您有什么建议可以在更大一点的程序中实现这种行为?如果在代码更深处的某个地方触发了应该退出程序的错误条件,您如何始终干净地返回 main?
  • @Janusz,在这种情况下,您可以使用/抛出异常,如果不返回预定义的值,即从函数中返回值,例如成功时返回 0,成功时返回 1失败的情况下,但继续执行,失败的情况下为-1并退出程序。根据函数的返回值,如果失败,则在执行更多清理活动后从主函数返回。最后,谨慎使用exit,我不是要回避。
  • @NarendraN,“必要的清理”是模糊的 - 操作系统会注意(Windows/Linux)内存和文件句柄被正确释放。至于“丢失”文件输出:如果您坚持认为这可能是一个真正的问题,请参阅stackoverflow.com/questions/14105650/how-does-stdflush-work 如果您遇到错误情况,正确的日志记录会告诉您程序达到未定义状态,您可以在之前设置断点您的日志记录点。这如何使调试变得更加困难?
  • 请注意,此答案是从问题 How to quit a C++ program? – SO 1116493 合并而来的。这个答案是在提出这个问题之前大约 6 年写的。
  • 对于现代 C++ 使用 return (EXIT_SUCCESS); 而不是 return(0)
【解决方案3】:

调用std::exit 函数。

【讨论】:

  • 调用该函数时会调用哪些对象的析构函数?
  • exit() 不返回。因此不会发生堆栈展开。甚至全局对象都不会被破坏。但是使用 atexit() 注册的函数会被调用。
  • 当您的库代码在我的主机进程中运行时,请不要调用exit() - 后者将在不知名的地方退出。
  • 请注意,此答案是从问题 How to quit a C++ program? – SO 1116493 合并而来的。这个答案是在提出这个问题之前大约 6 年写的。
【解决方案4】:

人们说“调用退出(返回代码)”,但这是一种不好的形式。在小程序中这很好,但是这样做有很多问题:

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

真的,您应该退出问题的唯一时间是 main.cpp 中的这一行:

return 0;

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

【讨论】:

  • 在多线程环境中,在不同线程中抛出的异常不会通过 main() 处理——在从属线程到期之前需要进行一些手动的跨线程通信。
  • 1.和 2. 取决于程序员,通过正确的日志记录这不是问题,因为执行永远停止。至于 3:这完全是错误的,操作系统将释放内存 - 可能不包括嵌入式设备/实时,但如果你这样做,你可能知道你的东西。
  • 请注意,此答案是从问题 How to quit a C++ program? – SO 1116493 合并而来的。这个答案是在提出这个问题之前大约 6 年写的。
  • 如果发生错误,您不应该返回 0。您应该返回 1(或者可能是其他值,但 1 始终是一个安全的选择)。
【解决方案5】:

return 0; 将其放在int main() 中的任何位置,程序将立即关闭。

【讨论】:

  • the-nightman、@evan-carslake 和其他所有人,我还要补充一点,这个 SO question #36707 讨论了 return 语句是否应该只出现在例程中的任何地方。这是一个解决方案;但是,根据具体情况,我不会说这是最好的解决方案。
  • OP 对此只字未提,所以我认为假设代码应该在函数 main 内是相当轻率的。
  • @localhost 在任何情况下,任何条件下,return 语句都是完全可选的。但是,int main() 不同。该程序使用int main() 开始和结束。没有其他方法可以做到这一点。程序将一直运行,直到您返回 true 或 false。几乎所有时间你都会返回 1 或 true,以表明程序已正确关闭(例如:如果你无法释放内存或任何原因,可能会使用 false。)让程序自行运行完成总是一个坏主意,例如: int main() { int x = 2; int foo = x*5; std::cout &lt;&lt; "blah"; }
  • @EvanCarslake 明白了,如果你注意到我在别处就这个问题发表的评论,我对int main 很熟悉;但是,在主程序中包含多个 return 语句并不总是一个好主意。一个潜在的警告是提高代码的可读性。但是,使用诸如更改状态的布尔标志之类的东西来防止代码的某些代码部分在该应用程序状态下运行。就个人而言,我发现一个常见的 return 语句使大多数应用程序更具可读性。最后,关于内存清理和退出前正确关闭 I/O 对象的问题。
  • @EvanCarslake 您不会从 main 返回 true 以表示正确的终止。您必须返回零或EXIT_SUCCESS(或者,如果需要,false,它将被隐式转换为零)以指示正常终止。要表示失败,您可以返回EXIT_FAILURE。任何其他代码含义都是实现定义的(在 POSIX 系统上,它意味着实际的错误代码)。
【解决方案6】:

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

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

【讨论】:

【解决方案7】:

要么从您的main 返回一个值,要么使用exit 函数。两者都取一个整数。除非您有一个外部进程监视返回值,否则返回什么值并不重要。

【讨论】:

【解决方案8】:

如果代码深处有错误,则抛出异常或设置错误代码。抛出异常总是比设置错误代码更好。

【讨论】:

【解决方案9】:

通常您会使用 exit() 方法和适当的 exit status

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

【讨论】:

  • 使用出口可能意味着您有设计问题。如果程序正确运行,它应该在 main return 0; 时终止。我假设exit()assert(false); 类似,因此只能在开发中使用以及早发现问题。
  • 请注意,此答案是从问题 How to quit a C++ program? – SO 1116493 合并而来的。这个答案是在提出这个问题之前大约 6 年写的。
【解决方案10】:

除了调用 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,并添加了上面的stub main。 这只是一个细节,因此在托盘和 main 中的 catch 之间没有太多代码。

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

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

总体而言,C 错误处理 - 退出和信号 - 和 C++ 错误处理 - 尝试/捕获/抛出异常 - 充其量是不一致的。

然后,在哪里检测到错误

throw "error message"

或更具体的异常类型。

【讨论】:

  • 顺便说一句:我很清楚,对于可能与运行相关的异常,在异常处理程序中或在异常处理程序周围使用可能涉及动态内存分配的数据类型不是一个好主意记不清。 C 风格的字符串常量不是问题。
  • 在你的程序中调用exit 是没有意义的。既然你在main,你就可以return exitCode;
【解决方案11】:

如果我正在测试的情况真的是坏消息,我会这样做:

*(int*) NULL= 0;

这给了我一个很好的核心转储,我可以从中检查情况。

【讨论】:

  • 这可能会被优化,因为它会导致未定义的行为。事实上,有这样一条语句的整个执行分支都可以被编译器清除掉。
  • 可以避免被优化。这是一种有趣的方法 - 这是否被证明在该领域有用?
【解决方案12】:

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

所以你需要添加一个预处理器。

include stdlib.h放在标题部分

然后在任何你喜欢的地方使用exit();,但记得在exit的括号中输入一个整数。

例如:

exit(0);

【讨论】:

    【解决方案13】:

    如果你的 if 语句在循环中你可以使用

     break; 
    

    如果你想转义一些代码并继续循环使用:

    继续;

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

     return 0;
    
    Or 
    
    
    
    
      exit();
    

    【讨论】:

      【解决方案14】:

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

      所以,在你的情况下,它会是:

          if(x==1)
          {
              return 0;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-01
        • 1970-01-01
        • 2016-11-09
        • 1970-01-01
        • 1970-01-01
        • 2020-04-30
        相关资源
        最近更新 更多