web-dev-qa-db-ja.com

コンパイラは、ヒープからスタック割り当てまで最適化できますか?

コンパイラの最適化に関する限り、ヒープ割り当てをスタック割り当てに変更することは合法であるか、可能ですか?または、それは as-ifルール を破るでしょうか?

たとえば、これが元のバージョンのコードであるとします

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

コンパイラはこれを次のように変更できますか

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

元のバージョンがカスタムアロケーターのようなものに依存していた場合、それは意味を持つので、私はそうは思わないでしょう。規格はこれについて具体的に何か述べていますか?

63
CoryKramer

はい、合法です。 expr.new/10のC++ 14:

実装では、置換可能なグローバル割り当て関数(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の割り当て呼び出しから返された値は、最初の引数として割り当て解除関数に渡されます。

—それ以外の場合、割り当てが拡張されたか、別のnew-expressionの割り当てを拡張することによって提供され、拡張new-expressionによって提供されるストレージを持つnew-expressionによって生成される他のすべてのポインター値のdelete-expressionが評価された場合、delete-expressionは割り当て解除関数を呼び出します。拡張new-expressionの割り当て呼び出しから返された値は、割り当て解除関数への最初の引数として渡されます。

—それ以外の場合、delete-expressionは割り当て解除関数を呼び出しません(3.7.4.2)。

したがって、要約すると、newdeleteを、ヒープの代わりにスタックを使用するなど、定義された実装で置き換えることは合法です。

注:Massimiliano Janesがコメントしているように、コンパイラは、do_something throws:この場合、コンパイラはfのデストラクタ呼び出しを省略します(この場合、変換されたサンプルはデストラクタを呼び出します)。しかし、それ以外は、fをスタックに自由に入れることができます。

50
geza

これらは同等ではありません。 f.do_something()がスローする場合があります。その場合、最初のオブジェクトはメモリ内に残り、2番目のオブジェクトは破壊されます。

6
lorro

他の回答では、IMOが十分に強調していないことを指摘したいと思います。

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

割り当てnew Foo()は通常、次の理由で置き換えることができません。

実装では、置換可能なglobal割り当て関数(18.6.1.1、18.6.1.2)の呼び出しを省略することができます。その場合、ストレージは代わりに実装によって提供されるか、別の新しい式の割り当てを拡張することによって提供されます。

したがって、上記のFooの例のように、_Foo::operator new_を呼び出す必要があります。この呼び出しを省略すると、プログラムの観察可能な動作が変更されます。

実際の例:Foosは、適切に機能するために(メモリマップIOなどの)特別なメモリ領域に存在する必要がある場合があります。

4
Daniel Jour