ダングリングポインタを比較することは合法ですか?
int *p, *q;
{
int a;
p = &a;
}
{
int b;
q = &b;
}
std::cout << (p == q) << '\n';
p
とq
の両方がすでに消えたオブジェクトをどのように指しているかに注意してください。これは合法ですか?
はじめに:最初の問題は、p
の値を使用することが合法かどうかです。
a
が破棄された後、p
は無効なポインター値として知られているものを取得します。 N44 からの引用(N4430のステータスの説明については、以下の「注」を参照してください):
ストレージ領域の期間の終わりに達すると、割り当て解除されたストレージの任意の部分のアドレスを表すすべてのポインターの値は、無効なポインター値になります。 )。
無効なポインタ値が使用された場合の動作も、N4430の同じセクションで説明されています(C++ 14 [basic.stc.dynamic.deallocation]/4にはほぼ同じテキストが表示されます)。
無効なポインター値を介した間接参照および無効なポインター値を割り当て解除関数に渡すことは、未定義の動作をします。無効なポインタ値のその他の使用には、実装定義の動作があります。
[脚注:一部の実装では、無効なポインタ値をコピーすると、システムで生成されたランタイム障害が発生することが定義されている場合があります。 —脚注を終了]
したがって、ここで何が起こるかを見つけるために、実装のドキュメントを参照する必要があります(C++ 14以降)。
上記の引用符のseという用語 means C++ 14 [conv.lval/2]のように、左辺値から右辺値への変換が必要です。
左辺値から右辺値への変換が式eに適用され、[...] glvalueが参照するオブジェクトに無効なポインター値が含まれている場合、動作は実装によって定義されます。
履歴:C++ 11では、これは実装定義ではなく未定義と述べました。 DR1438 によって変更されました。完全な引用については、この投稿の編集履歴を参照してください。
p == q
への適用:C++ 14 + N4430で、p
とq
の評価結果が実装定義であることを受け入れたと仮定します。また、実装では、ハードウェアトラップが発生することを定義していません。 [expr.eq]/2は言う:
2つのポインターは、両方がnullの場合、両方が同じ関数を指している場合、または両方が同じアドレス(3.9.2)を表している場合は等しく比較されます。それ以外の場合は、等しくありません。
p
とq
が評価されるときに取得される値は実装によって定義されるため、ここで何が起こるかはわかりません。ただし、実装定義または未指定のいずれかである必要があります。
この場合、g ++は不特定の動作を示すように見えます。 -O
スイッチに応じて、b
が破棄された後に同じメモリアドレスがa
に再利用されたかどうかに応じて、1
または0
のいずれかを表示させることができました。
N4430に関する注意:これはC++ 14に対して提案された欠陥解決であり、まだ受け入れられていません。オブジェクトの存続期間、無効なポインター、サブオブジェクト、共用体、および配列境界アクセスを取り巻く多くの文言をクリーンアップします。
C++ 14のテキストでは、[basic.stc.dynamic.deallocation]/4以降の段落で、delete
を使用すると無効なポインタ値が発生することが定義されています。ただし、同じ原則が静的ストレージまたは自動ストレージに適用されるかどうかは明確に述べられていません。
[basic.compound]/3には「有効なポインタ」という定義がありますが、曖昧すぎて賢明に使用できません。[basic.life]/5(脚注)は同じテキストを参照して、のオブジェクトへのポインタの動作を定義します。静的ストレージ期間。これは、すべてのタイプのストレージに適用することを意図していたことを示しています。
N4430では、テキストはそのセクションから1レベル上に移動されるため、すべての保存期間に明確に適用されます。添付のメモがあります:
ドラフティングノート:これは、動的ストレージ期間だけでなく、終了できるすべてのストレージ期間に適用する必要があります。スレッドまたはセグメント化されたスタックをサポートする実装では、スレッドと自動ストレージは動的ストレージと同じように動作する場合があります。
私の意見:p
が無効なポインタ値を取得すると言う以外に、標準(N4430より前)を解釈する一貫した方法がわかりません。この動作は、すでに確認した以外のセクションではカバーされていないようです。したがって、この場合、N4430の文言を標準の意図を表すものとして扱うことができてうれしいです。
歴史的に、ポインタを右辺値として使用すると、システムがそのポインタの一部のビットで識別される情報をフェッチする可能性があるシステムがいくつかありました。たとえば、ポインタにオブジェクトのヘッダーのアドレスとオブジェクトへのオフセットを含めることができる場合、ポインタをフェッチすると、システムはそのヘッダーからいくつかの情報もフェッチする可能性があります。オブジェクトが存在しなくなった場合、そのヘッダーから情報をフェッチしようとして失敗すると、任意の結果が生じる可能性があります。
そうは言っても、C実装の大多数では、ある特定の時点で生きていたすべてのポインターは、その特定の時点と同じ関係演算子と減算演算子に関して永久に同じ関係を保持します。実際、ほとんどの実装では、char *p
がある場合、char *base; size_t size;
;かどうかをチェックすることにより、(size_t)(p-base) < size
で識別されるオブジェクトの一部を識別するかどうかを判断できます。このような比較は、オブジェクトの存続期間に重複がある場合でも、遡及的に機能します。
残念ながら、標準では、コードが後者の保証のいずれかを必要とすることを示すことができる手段を定義していません。また、特定の実装が後者の動作のいずれかを約束できるかどうかをコードが尋ね、そうでない場合はコンパイルを拒否できる標準的な手段もありません。 。さらに、一部のハイパーモダン実装では、2つのポインターでのリレーショナル演算子または減算演算子の使用を、問題のポインターが常に同じライブオブジェクトを識別するというプログラマーの約束と見なし、その仮定の場合にのみ関連するコードを省略します。保持しませんでした。その結果、多くのハードウェアプラットフォームが多くのアルゴリズムに役立つ保証を提供できる場合でも、コードが自然に提供されないハードウェアで実行する必要がない場合でも、コードがそのような保証を利用できる安全な方法はありません。