编译器可以从堆优化到堆栈分配吗?

时间:2017-11-02 12:54:05

标签: c++ memory-management language-lawyer compiler-optimization

就编译器优化而言,将堆分配更改为堆栈分配是否合法和/或可能?或者会打破as-if rule

例如,假设这是代码的原始版本

{
    Foo* f = new Foo();
    f->do_something();
    delete f;
}

编译器是否能够将其更改为以下

{
    Foo f{};
    f.do_something();
}

我不会这么认为,因为如果原始版本依赖于自定义分配器之类的东西,这会产生影响。标准是否对此有具体说明?

3 个答案:

答案 0 :(得分:50)

是的,这是合法的。 C ++ 14的expr.new/10

  

允许实现省略对可替换全局的调用   分配函数(18.6.1.1,18.6.1.2)。当它这样做时,存储   而是由实现提供或通过扩展提供   分配另一个新表达式。

expr.delete/7

  

如果delete-expression的操作数值不为null   指针值,然后:

     

- 如果分配调用对象的new-expression   删除没有被省略,分配没有延长(5.3.4),   delete-expression应调用释放函数(3.7.4.2)。   从new-expression的分配调用返回的值   应作为释放函数的第一个参数传递。

     

- 否则,如果分配已延长或由提供   扩展另一个新表达式的分配,以及   delete-expression表示由a生成的每个其他指针值   new-expression,具有由扩展提供的存储   new-expression已被评估,delete-expression应调用a   释放功能。从分配调用返回的值   扩展的new-expression应作为第一个参数传递给   释放功能。

     

- 否则, delete-expression不会调用deallocation   功能(3.7.4.2)。

因此,总而言之,将newdelete替换为定义的实现是合法的,例如使用堆栈而不是堆。

注意:正如Massimiliano Janes所评论的那样,如果do_something抛出,编译器就不能完全坚持你的样本转换:在这种情况下编译器应该省略f的析构函数调用(当你转换时)在这种情况下,sample会调用析构函数)。但除此之外,可以将f放入堆栈中。

答案 1 :(得分:6)

这些不等同。 <{1}}可能会抛出,在这种情况下,第一个对象保留在内存中,第二个对象被破坏。

答案 2 :(得分:4)

我想指出IMO在其他答案中没有给予足够的重视:

struct Foo {
    static void * operator new(std::size_t count) {
        std::cout << "Hey ho!" << std::endl;
        return ::operator new(count);
    }
};

通常无法替换分配new Foo(),因为:

  

允许实现省略对可替换的全局分配函数的调用(18.6.1.1,18.6.1.2)。当它这样做时,存储由实现提供,或者通过扩展另一个新表达式的分配来提供。

因此,与上面的Foo示例一样,需要调用Foo::operator new。省略此调用将改变程序的可观察行为。

真实世界的例子:Foo可能需要驻留在某个特殊的内存区域(如内存映射的IO)才能正常运行。