44

デフォルトの配置new演算子は、18.6 [support.dynamic] ¶1 でスローしない例外仕様で宣言されています。

void* operator new (std::size_t size, void* ptr) noexcept;

ただし、5.3.4 [expr.new] ¶15によれreturn ptr;noexcept、これは、オブジェクトのコンストラクターを呼び出す前に、コンパイラーが null を返さないことを確認する必要があることを意味します。

-15-
[注:例外をスローしない例外仕様 (15.4) で割り当て関数が宣言されていない限り、例外をスローしてストレージの割り当てに失敗したことを示しstd::bad_allocます (第 15 節、18.6.2.1)。それ以外の場合は、null 以外のポインターを返します。割り当て関数が例外をスローしない仕様で宣言されている場合、記憶域の割り当てに失敗したことを示すために null を返し、それ以外の場合は null 以外のポインターを返します。割り当て関数が null を返す場合、初期化は行われず、割り当て解除関数は呼び出されず、new-expression の値は null になります。

(一般的にではなく、具体的には Placementnewの場合)この null チェックは、小さいながらも残念ながらパフォーマンスに影響を与えるようです。

newコンパイラのコード生成を改善するために、パフォーマンスが非常に重要なコード パスで配置が使用されているコードをデバッグしており、アセンブリで null のチェックが観察されました。例外をスローする仕様で宣言されたクラス固有の配置newオーバーロードを提供することにより (スローできない可能性がありますが)、条件分岐が削除され、コンパイラは周囲のインライン関数に対してより小さなコードを生成することもできました。配置new関数がスローできなくても、スローできると言う結果は、測定可能なほど優れたコードでした。

そのため、配置の場合にnullチェックが本当に必要かどうか疑問に思っていましたnew。null を返すことができる唯一の方法は、null を渡す場合です。次のように書くことは可能であり、明らかに合法ですが、

void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );

なぜそれが役立つのかわかりません。プログラマーが配置を使用する前に null を明示的にチェックする必要がある場合は、より良いと思いますnew

Obj* obj = ptr ? new (ptr) Obj() : nullptr;

newnull ポインターのケースを正しく処理するために配置が必要だった人はいますか? (つまりptr、有効なメモリ位置である明示的なチェックを追加しません。)

newnull ポインターをデフォルトの配置関数に渡すことを禁止することが合理的かどうか、もしそうでない場合は、コンパイラーに値が null ではないことを伝えようとする以外に、不要な分岐を回避するためのより良い方法があるかどうか疑問に思っています。

void* ptr = getAddress();
(void) *(Obj*)ptr;   // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();

または:

void* ptr = getAddress();
if (!ptr)
  __builtin_unreachable();  // same, but not portable
Obj* obj = new (ptr) Obj();

注意: この質問は意図的にマイクロ最適化にタグ付けされています。パフォーマンスを「改善」するために、すべてのタイプの配置をオーバーロードすることをお勧めしません。newこの影響は、非常に特定のパフォーマンスが重要なケースで、プロファイリングと測定に基づいて確認されました。

更新: DR 1748により、null ポインターを新しい配置で使用する動作が未定義になるため、コンパイラーはチェックを行う必要がなくなりました。

4

1 に答える 1

14

「null ポインターのケースを正しく処理するために新しい配置が必要だった人はいますか?」以外の質問はあまり見当たりませんが、(私はしていません)、この事件は、この問題についていくつかの考えをこぼすのに十分興味深いと思います.

私は、配置の新しい機能と一般的な割り当て機能の要件に関して、標準が壊れているか不完全であると考えています。

引用された §5.3.4,13 をよく見ると、返された nullpointer がない場合でも、すべてのnoexcept割り当て関数をチェックする必要があることを意味します。したがって、次のように書き換える必要があります。

スローしない例外仕様で割り当て関数が宣言され、 null を返す場合、初期化は行われず、解放関数は呼び出されず、new 式の値は null になります。

§3.7.4.1に従わなければならないため、例外をスローする割り当て関数の有効性を損なうことはありません。

[...] 成功した場合、要求されたサイズ以上の長さのストレージ ブロックの開始アドレスを返します。[...] 返されるポインターは、基本的なアライメント要件 (3.11) を持つ完全なオブジェクト型のポインターに変換できるように適切にアライメントされ、割り当てられたストレージ内のオブジェクトまたは配列にアクセスするために使用されます (ストレージは、対応する割り当て解除関数の呼び出しによって明示的に割り当て解除されます)。

そして§5.3.4,14 :

[ 注: 割り当て関数が null 以外の値を返す場合、それは、オブジェクト用のスペースが予約されているストレージのブロックへのポインターでなければなりません。ストレージのブロックは、適切に位置合わせされ、要求されたサイズであると想定されます。[...] -終わりの注]

明らかに、指定されたポインターを返すだけの配置 new では、利用可能なストレージ サイズとアライメントを適切にチェックできません。したがって、

§18.6.1.3,1配置について new は言う

[...] (3.7.4) の規定は、演算子 new および演算子削除のこれらの予約配置形式には適用されません。

(彼らはその場所で §5.3.4,14 について言及し忘れていたと思います。)

ただし、これらの段落を合わせると、間接的に「§5.3.4,14 に違反しているため、ガベージ ポインターを palcement 関数に渡すと UB が発生します」と述べられています。そのため、新しい配置に与えられた任意のポイントナーの健全性をチェックするのはあなた次第です。

その精神と、書き換えられた §5.3.4,13 により、標準はnoexceptfrom 配置 new を取り除くことができ、その間接的な結論への追加につながります:「...そして、null を渡すと、UB も取得します」。一方、null ポインターを使用する場合よりも、位置合わせされていないポインターまたはメモリが少なすぎるポインターを使用する可能性ははるかに低くなります。

ただし、これによりnullをチェックする必要がなくなり、「必要のないものにはお金を払わない」という哲学にうまく適合します。§18.6.1.3,1 に明示的に記載されているため、割り当て関数自体をチェックする必要はありません。

まとめると、2 番目のオーバーロードを追加することを検討できます。

 void* operator new(std::size_t size, void* ptr, const std::nothrow_t&) noexcept;

悲しいことに、これを委員会に提案しても変更が生じる可能性は低いです。というのは、配置 new がヌル ポインターで問題ないことに依存している既存のコードが壊れてしまうからです。

于 2013-07-10T14:31:25.970 に答える