GodboltのCompilerExplorerをいじりました。特定の最適化がいかに優れているかを見たかった。私の最小作業例は次のとおりです。
#include <vector>
int foo() {
std::vector<int> v {1, 2, 3, 4, 5};
return v[4];
}
生成されたアセンブラー(clang 5.0.0、-O2 -std = c ++ 14による):
foo(): # @foo()
Push rax
mov edi, 20
call operator new(unsigned long)
mov rdi, rax
call operator delete(void*)
mov eax, 5
pop rcx
ret
ご覧のとおり、clangは答えを知っていますが、戻る前に多くのことを行います。 「operator new/delete」のため、ベクターでさえ作成されているように思えます。
ここで何が起こるのか、なぜ戻って来ないのか、誰にも説明できますか
GCCによって生成されたコード(ここではコピーされません)は、ベクトルを明示的に構築しているようです。 GCCが結果を推測できないことを誰もが知っていますか?
std::vector<T>
は、動的割り当てを含むかなり複雑なクラスです。 clang++
ヒープの割り当てを省略できる場合があります 、これはかなりトリッキーな最適化であり、それに依存するべきではありません。例:
int foo() {
int* p = new int{5};
return *p;
}
foo(): # @foo() mov eax, 5 ret
例として、std::array<T>
(動的に割り当てない)完全にインライン化されたコードを生成する :
#include <array>
int foo() {
std::array v{1, 2, 3, 4, 5};
return v[4];
}
foo(): # @foo() mov eax, 5 ret
Marc Glisse が他の回答のコメントで指摘されているように、これは規格が [expr.new]#1 で述べていることです:
実装では、置換可能なグローバル割り当て関数([new.delete.single]、[new.delete.array])の呼び出しを省略することができます。その場合、ストレージは代わりに実装によって提供されるか、別の新しい式の割り当てを拡張することによって提供されます。実装は、割り当てが拡張されなかった場合に以下が当てはまる場合、new-expression e1の割り当てを拡張してnew-expression e2のストレージを提供できます。[...]
コメントにあるように、operator new
は置き換えることができます。これはどの翻訳単位でも発生する可能性があります。したがって、置き換えられないケースに合わせてプログラムを最適化するには、プログラム全体の分析が必要です。そして、それがisに置き換えられた場合、もちろんそれを呼び出さなければなりません。
デフォルトのoperator new
はライブラリですI/O呼び出しは指定されていません。ライブラリI/O呼び出しは監視可能であり、したがって最適化することもできないため、これは重要です。
N3664 の[expr.new]への変更は、1つの回答と1つのコメントで引用されており、new-expressionsが置換可能なグローバル割り当てを呼び出さないようにします。関数。ただし、vector
は、new-expressionではなく、std::allocator<T>::allocate
を直接呼び出す::operator new
を使用してメモリを割り当てます。そのため、特別な許可は適用されず、通常、コンパイラは::operator new
へのそのような直接呼び出しを排除できません。
ただし、std::allocator<T>::allocate
の仕様には this があるため、すべての希望が失われるわけではありません。
備考:ストレージは
::operator new
を呼び出すことで取得されますが、この関数が呼び出されるタイミングや頻度は指定されていません。
この許可を活用して、libc ++のstd::allocator
特別なclangビルトインを使用 は、コンパイラーに省略が許可されていることを示します。 -stdlib=libc++
を使用すると、 clangでコードがコンパイルされます
foo(): # @foo()
mov eax, 5
ret