就编译器优化而言,将堆分配更改为堆栈分配是否合法和/或可能?或者会打破as-if rule?
例如,假设这是代码的原始版本
{
Foo* f = new Foo();
f->do_something();
delete f;
}
编译器是否能够将其更改为以下
{
Foo f{};
f.do_something();
}
我不会这么认为,因为如果原始版本依赖于自定义分配器之类的东西,这会产生影响。标准是否对此有具体说明?
答案 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)。
因此,总而言之,将new
和delete
替换为定义的实现是合法的,例如使用堆栈而不是堆。
注意:正如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)才能正常运行。