C++ProgrammingC++ デベロッパー

消去されたオブジェクトのアドレスでプレースメント・ニューによって作成されたオブジェクトにアクセスすることが、std::launderなしで未定義の動作を引き起こす理由は何ですか?ストレージは有効のままですが。

Hintsage AIアシスタントで面接を突破

質問への回答

オブジェクトが消去され、同じアドレスでプレースメント・ニューを介して新しいオブジェクトが作成されると、C++ のポインタの由来ルールは元のポインタ値が自動的に新しいオブジェクトを指すわけではないと述べています。コンパイラは特定のタイプのポインタがオブジェクトのライフタイム全体にわたってそのオブジェクトのアイデンティティを維持することを前提とし、タイプベースのエイリアス分析に基づく積極的な最適化を可能にします。std::launder は新しいオブジェクトを指すポインタを明示的に作成し、ストレージが今や異なるタイプまたはconst/volatileの資格を持つ別のオブジェクトを含んでいることをコンパイラに伝えます。この介入がなければ、古いポインタの逆参照は厳密なエイリアシングルールに違反し、有効なストレージが含まれているにもかかわらず未定義の動作が発生します。

生活からの状況

リアルタイムオーディオ処理エンジンが固定のバッファプールを再利用してCPUキャッシュミスを最小限に抑え、ライブパフォーマンス中のヒープフラグメンテーションを避けることを検討してください。

解決策1: 標準ヒープ割り当て

最初のプロトタイプは、各処理ブロックのために新しいオーディオフレームオブジェクトをnewを使用して割り当てました。シンプルでしたが、これによりガーベジコレクションの一時停止中や非連続メモリへのアクセス時に可聴のドロップアウトやキャッシュミスが発生し、プロフェッショナルオーディオには受け入れられませんでした。

解決策2: 生ポインタによるプレースメント・ニュー

チームはstd::aligned_storage_tの事前に割り当てられた配列に切り替え、プレースメント・ニューを使用してフレームをその場で構築しました。しかし、再構築後に元のポインタ値を単に再利用しました。Clangでの最適化ビルドでは、前のフレームのconstボリュームメンバーを指すポインタが有効であるとコンパイラが仮定し、新しいフレームが異なるデータを保持しているメモリから再読み込みするのではなく、レジスタから古い値を再利用してしまいました。

解決策3: std::launderの実装

彼らはプレースメント・ニュー操作の後にstd::launderを導入し、新しいオブジェクトのライフタイムへのポインタを取得しました。これにより、コンパイラはメモリが今や異なる値を持つ新しいオブジェクトを保持していることを認識せざるを得なくなり、消去されたフレームからのconstメンバーの不正なレジスタキャッシュを防ぎました。

この解決策は、音声のグリッチを排除しながらゼロ割り当てのパフォーマンスを維持し、サブミリ秒のレイテンシ要件を達成しました。

候補者がしばしば見逃すこと


std::launderは、デストラクタを呼び出すことなくアクティブオブジェクトのタイプを変更するために使用できますか?

いいえ、std::launderはオブジェクトのライフタイムを延長または変更することはありません。標準は、古いオブジェクトのライフタイムが終了(デストラクタが呼ばれた)し、同じストレージ内で新しいオブジェクトのライフタイムが始まっている必要があると明示的に要求しています。ライフタイムが終了していないオブジェクトへのポインタを洗浄しようとすると未定義の動作が発生します。なぜなら、C++ 抽象マシンは元のオブジェクトがそのアドレスにまだ存在すると見なすからです。


std::launderはポインタの基盤のビットパターンを変更しますか?

いいえ、std::launderは元のアドレスと等しいポインタ値を生成しますが、異なる由来情報を持っています。実装は通常、全く同じビットパターンを返しますが、この操作は単なるキャストではなく、このポインタが新しいオブジェクトを指すことをコンパイラのエイリアス分析に通知します。この違いは、コンパイラが翻訳ユニットを越えて全プログラム最適化を行う際にクリティカルになります。


トリビアルに破壊可能なタイプにはデストラクタがないため、std::launderは不要ですか?

トリビアルに破壊可能なタイプでも、オブジェクトのライフタイムが終了し、新しいオブジェクトが同じストレージで作成される場合、std::launderが必要です。オブジェクトのライフタイムは、そのストレージが再利用されると終了します。デストラクタが実行されるかどうかに関係なくです。std::launderがなくては、コンパイラは古いポインタを通じてアクセスした場合に古いオブジェクトのconstメンバーが不変であると仮定し、異なるconstメンバー値を持つ新しいオブジェクトのプレースメント・ニューの後でも最適化バグが発生する可能性があります。