【问题标题】:Getting an extra call to the copy c-tor while introducing a scope在引入范围时额外调用复制 c-tor
【发布时间】:2011-12-15 15:32:00
【问题描述】:

原始代码

#include <iostream>
int global;
struct A
{
   A(){}
   A(const A&x){
       ++global;
   }
   ~A(){}
};
A foo()
{  
     A a;
     return a;  
}
int main()
{
   A x = foo();
   std::cout << global;
}

在支持命名返回值优化的优化编译器上,输出将是 0

当我将foo 的定义更改为

A foo()
{ 
  { 
     A a;
     return a;  
  }
}

我得到1 作为输出,即复制 c-tor 被调用一次。可能的原因是什么?引入虚拟作用域会完全改变代码的行为。我错过了什么?

我在 g++ 编译器上对其进行了测试。这里有任何编译器人员可以以某种特定于实现的方式解释该场景吗?

编辑

我在 clang 上对其进行了测试,即使在第二种情况下,它也优化了对复制 c-tor 的调用。

Andrew Pinski(gcc 专家)证实这确实是 g++ 错过优化的一个案例。

【问题讨论】:

  • 这样的优化可以应用或不应用在编译器的心血来潮。试图对它们进行推理是……愚蠢的。没有?
  • 因为编译器需要非常严格地应用优化。如果其任何预条件失败,则无法应用优化。对人类来说,它可以在这里做到这一点似乎很明显,但是在一般情况下添加额外的范围会增加一些复杂的预条件检查失败。这里唯一好的答案是@Tomalak,因为优化是由编译器选择完成还是不完成(启发式)。
  • @Prasoon:好的。 Output would be 0 on an optimized compiler 向我暗示您希望在 任意 优化的编译器上获得可重现的结果,而不是在您的 PC 上仅运行一次。
  • @Prasoon:它询问 RVO 是否“普遍保证”,给出的答案是“否”。一周后,你发布了这个,显然很惊讶你的 RVO 在某种情况下消失了。大声笑!
  • @PrasoonSaurav:(a) RVO 跨实现行为。 (b) 那太糟糕了;我发现幽默感对于富有成效的生活至关重要。

标签: c++ optimization scope g++ nrvo


【解决方案1】:

除了编译器不够聪明,无法看到虚拟作用域(由额外的括号引入)没有任何区别之外,我没有看到任何其他原因,至少对于这个特定的代码而言。编译器被多余的括号愚弄了;它可能对函数体的其余部分(甚至不存在)做出了疯狂的假设。

0 或 1,无论哪种方式,行为都完全符合标准,因为标准不要求编译器生成0(或1)。如您所知,这取决于编译器。

至于foo在这两种情况下生成的汇编代码只有一点区别:

  • 第一个代码:

    __Z3foov:
    LFB992:
         .cfi_startproc
         movl  4(%esp), %eax
         ret   $4
         .cfi_endproc
    
  • 第二个代码:

    __Z3foov:
    LFB992:
         .cfi_startproc
         incl _global      <----- incrementing the global. God knows why!
         movl  4(%esp), %eax
         ret   $4
         .cfi_endproc
    

我使用了g++ -O6。版本:MinGW (GCC) 4.6.1

【讨论】:

  • @Downvoter:请说明原因。或者甚至更好地发布答案,因为您似乎知道第二种情况下输出背后的基本原理
  • @PrasoonSaurav:为什么?无神论/有神论与此有什么关系? :| (或者您是在暗示无神论者是非理性的,因此即使是非理性也能够提供理由?:P)
  • @Nawaz : incrementing the global. God knows why! :)
【解决方案2】:

理论上你在这两种情况下都可以得到 1。这是编译器可能会也可能不会优化复制构造函数的情况之一。

您可以通过谷歌搜索“返回值优化”“命名返回值优化”找到有关该主题的更多详细信息,在您的情况下是后者。 p>

请注意,如果您将代码更改为:

A foo()
{ 
  { 
     return A();
  }
}

然后 RVO 应该启动,您将在输出中获得 0。

在您描述的情况下,为什么 NRVO 没有介入? (我已经在 GCC 4.6 上确认了这一点。) 我现在不确定;要么编译器不够聪明,要么这里有一条关于 NRVO 的规则不允许它。


编辑:

标准说...

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数有副作用。 (...) 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的:

— 在具有类返回类型的函数的 return 语句中,当表达式是具有与函数返回类型,复制/移动操作可以通过构造省略 自动对象直接转化为函数的返回值

因此这里是允许的,但是编译器不够聪明,无法在这里执行 NRVO。 如果你在 GCC 上工作,你可以检查它在 Clang 上是否相同,看看结果是否不同(我的直觉告诉我会的)。

请注意,RVO 和 NRVO 是编译器功能的名称,而“复制省略”是标准对这种行为的一般引用方式。

【讨论】:

  • 我已经扩展了我的答案。 @downvoter,请重新评估当前版本。
  • Clang 在第二个版本中优化了对复制 c-tor 的调用。
【解决方案3】:

使用哪个编译器?这两个结果都是合法的,所以正式地,你不能 投诉。实际上,我不明白为什么应该引入范围 改变任何东西;作为实施质量问题,我认为你 可以投诉。

【讨论】:

【解决方案4】:

编译器可能会错过某些情况,即使在其他情况下进行优化也是可能的。 AFAIK——我现在检查了允许复制构造函数省略的地方——没有约束变量必须在函数的顶级范围内,它必须只是一个自动变量。

【讨论】:

  • 那是哪一段?我试图找到它,但失败了
  • @TomalakGeret'kal,12.7/31 in n3290。
  • n3290 中没有 12.7/31。 [编辑:在 12.8/31、n3290 和 C++11 中找到]
  • @TomalakGeret'kal。 7 是一个错字,我的意思是 12.8/31。
【解决方案5】:

当您按值返回时会有一个副本。优化设置可以(应该)永远不会改变程序的可见行为。

【讨论】:

  • en.wikipedia.org/wiki/Return_value_optimization -- 注意开头段落的第二句。
  • 这其实不是真的。你会认为是,但事实并非如此。尽管会改变副作用,但可以省略一些副本。
  • 在这种情况下,标准明确赋予编译器忽略来自复制构造函数的可观察行为的权利。
  • 该标准允许删除复制构造函数(即使它们有副作用),只要其余代码不变。因此,这里编译器可以使用 RVO 并在调用站点构建返回值,从而无需复制。
猜你喜欢
  • 1970-01-01
  • 2013-12-08
  • 2014-04-02
  • 2019-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多