Zan*_*ynx 3 c++ optimization exception
在我看来,如果你有一些像这样的C++代码:
int f()
{
  try {
    if( do_it() != success ) {
      throw do_it_failure();
    }
  } catch( const std::exception &e ) {
    show_error( e.what() );
  }
}
C++编译器应该能够优化throw并捕获几乎简单的goto.
但是,从我查看反汇编和单步执行代码的经验来看,编译器总是跳过非常混乱的异常处理库.
他们为什么这样做?是否存在一些阻止优化的语言要求?如果它是:
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;
}
我认为所接受的答案即使没有错误,也是相当无用的,所以即使过了这么多年,我仍然觉得有必要提供一个正确的答案。
猜测为什么编译器实现者选择不在任何特定功能上投入精力只是……猜测。仅在特殊情况下抛出异常这一事实通常不会被认为是不优化此类代码性能的理由。相反,尽管抛出代码确实没有以牺牲非抛出代码为代价进行优化,但异常抛出和处理基础结构仍然经过非常仔细的优化。
此外,这段代码可能感觉很做作,不值得考虑,但事实并非如此:它可能是由于内联和优化更复杂的代码而产生的,优化它可能会产生更简单的代码,从而允许其他优化过程启动,或包含要进一步内联的函数。像这样的优化过程,当正确且高效地实现时,总是值得至少被考虑,无论原始代码看起来多么做作。否则,即使是像消除死代码这样的基本步骤也会被避免,因为“死代码不应该首先编写”。显然情况并非如此。
因此,我只是不同意已接受的答案。应该异常抛出异常并不是此代码未优化的原因。
原因纯粹是技术性的,clang 开发邮件列表中的这封电子邮件对此进行了解释:http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html
总而言之,该语言允许在catch块内调用的代码在“从未见过”异常对象的任何点重新抛出异常:
void g() { throw; }
因此,考虑OP代码:
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 开发人员(我们不能对其他编译器说什么)认为这种优化不值得,可能是因为它只适用于catch不执行功能的块来电。
不管怎样,该消息没有说明他们是否会接受补丁来实现这一点。
因为do_it()在扔之前可能抛出不同的异常do_it_failure();
至于你的第二个例子,编译器可以做到这一点,但它必须被视为一个特例,那么为什么还要为这种病态案例而烦恼呢?
他们为什么这样做?
因为C++例外适用于,特殊情况下的特殊情况和性能并不重要.
考虑到这一点,设计了C++的异常,确保编译器供应商可以在没有异常抛出的常见情况下提供接近最优的性能,但是在抛出异常的奇怪情况下,代价可能性能更差.
从一开始就鼓励用户仅在特殊情况下使用异常,并且鼓励实现者优化无异常情况(析构函数地址必须存储在某处以便在异常发生时调用析构函数),代价是异常案件.
虽然实施者当然可以花费资源来优化奇怪的例外情况,但大多数用户都不喜欢这样,因为总是有更重要的东西需要改进.