代码之家  ›  专栏  ›  技术社区  ›  Zan Lynx

C++异常抛出/ catch优化

  •  2
  • Zan Lynx  · 技术社区  · 14 年前

    在我看来,如果你有这样的C++代码:

    int f()
    {
      try {
        if( do_it() != success ) {
          throw do_it_failure();
        }
      } catch( const std::exception &e ) {
        show_error( e.what() );
      }
    }
    

    然而,根据我查看反汇编和单步执行代码的经验,似乎编译器 总是

    他们为什么这么做?是否有一些语言要求妨碍优化?如果是:

    int f()
    {
      try { throw std::runtime_error("Boo!"); }
      catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
    }
    

    为什么编译器不把它重写为

    int f()
    {
      std::cout << "Boo!" << std::endl;
    }
    
    2 回复  |  直到 14 年前
        1
  •  2
  •   Nicola Gigante    5 年前

    我认为被接受的答案即使不是错的,也是毫无意义的,所以即使在这么多年之后,我仍然觉得有必要给出一个正确的答案。

    猜测编译器实现者为什么选择不在任何特定的特性上下功夫只是,好吧。。。投机。只有在异常情况下才会抛出异常这一事实通常不会被认为是不优化此类代码性能的原因。相反,即使抛出代码确实没有得到优化

    此外,这段代码可能会让人觉得很做作,不值得考虑,但事实并非如此:它可能是对更复杂的代码进行内联和优化的结果,而优化它可能会导致生成更简单的代码,从而允许其他优化传递启动,或者进一步内联包含函数。像这样的优化过程,当正确且有效地实现时,总是值得至少考虑的,不管原始代码看起来有多么做作。否则,即使是像 将被避免,因为“死代码不应该写在第一位”。显然不是这样。

    原因纯粹是技术性的,这封来自clang development邮件列表的电子邮件对此进行了解释: http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html

    catch 阻止到 重新收缩

    void g() { throw; }
    

    因此,考虑操作代码:

    int f()
    {
      try { throw std::runtime_error("Boo!"); }
      catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
    }
    

    对于编译器来说, e.what() operator<< ,可能会重新引发异常,因此优化掉异常处理代码会破坏程序的语义。

    要确保不是这样,就需要“完整的程序知识”,如上面的电子邮件中所写。更简单的案例 能够

    int func() {
      try {
        throw 42;
      }catch(int x) {
        return x;
      }
    }
    

    以上代码 变成 return 42 . 没有技术上的原因阻碍它。

    godbolt ). 这一次,我们可以从一个实际的来源,上面链接的电子邮件中看出,Clang开发人员(我们不能对其他编译器说什么)认为这种优化不值得, 可能 因为它只适用于 抓住 不执行函数调用的块。

        2
  •  5
  •   James Curran    14 年前

    因为 do_it() 可以在抛出之前抛出不同的异常 do_it_failure();

    至于你的第二个例子,编译器可以做到这一点,但它必须被视为一个特殊的情况,所以为什么要费心这样一个病态的情况?

        3
  •  5
  •   sbi    14 年前

    他们为什么这么做?

    因为C++异常是为了, 特殊情况下的情况和表现并不重要。

    考虑到C++异常的情况下,确保编译器供应商可以在常见的情况下,在没有抛出异常的情况下提供接近最优的性能,而代价是比抛出异常时的奇数情况下的性能更差。

    从一开始,我们就鼓励用户仅在异常情况下使用异常,并鼓励实现人员优化无异常情况(必须将析构函数地址存储在某个位置,以便在出现异常时调用析构函数),代价是以异常情况为代价。
    虽然实现者当然可以花费资源来优化奇怪的异常情况,但大多数用户不会喜欢这样,因为总是有那么多更重要的事情需要改进。