歴史的に、C++の開発者はデバッガのブレークポイントをトリガーするために、プラットフォーム固有のロジックを手動で実装する必要がありました。異なるオペレーティングシステムやアーキテクチャによってデバッグ信号の処理方法が異なるため、Boost.Test、Catch2、Unreal Engineといったプロジェクトは、それぞれ独自の内部マクロ(例:MSVC用の`__debugbreak()`やClang用の`__builtin_debugtrap()`)を作成してきました。この標準化の欠如により、開発者は異なる環境間で一貫したデバッグ体験を確保するために、「難解な」プラットフォーム知識を維持することを強いられてきました。
C++26は、`std`名前空間に`breakpoint()`、`is_debugger_present()`、`breakpoint_if_debugging()`の3つの標準化された関数を提供する`<debugging>`ヘッダーを導入することでこの問題に対処します。これらの関数は「フリースタンディング」として設計されており、ベアメタルな組み込みシステムなど、オペレーティングシステムのない環境でも使用できます。これらの関数がそのようなコンテキストで動作することを保証するために、`is_debugger_present()`は`operator new`と同様に「置き換え可能」であり、標準的なOSレベルの検知が利用できない特定のハードウェアに対して、開発者が独自の実装を定義できるようにします。
`breakpoint()`が無条件の停止を提供し、`is_debugger_present()`がブーリアンステータスを返すのに対し、最も一般的なユースケースは`breakpoint_if_debugging()`によって処理されます。この便利な関数は両方を組み合わせたものであり、デバッガがプロセスをアクティブにトレースしている場合にのみ停止をトリガーします。単純なブレークポイントを超えて、これらの関数を分離することで、開発者は`is_debugger_present()`を他の診断タスクに使用できるようになります。例えば、よりデバッガフレンドリーなメモリアロケータへの切り替え、ステップ実行中のタイムアウトの無効化、あるいは追加の診断出力の生成などが挙げられます。ただし、実装の詳細は依然としてプラットフォームに依存します。例えば、Linuxでは標準が`/proc/self/status`の読み取りに依存する可能性があり、Windowsでは`IsDebuggerPresent()` APIをラップする可能性があります。
技術的なニュアンスの一つとして、これらの関数がハードウェア命令とどのように相互作用するかが挙げられます。x86アーキテクチャでは、`breakpoint()`は単一の`int3`命令にコンパイルされることが期待されます。一部の観察者は、このアプローチの有効性が、トラップの瞬間にデバッガが命令ポインタを正しく解釈できる能力に依存していると指摘しています。標準ではこれらの関数の動作を「実装定義」として定義しているため、指定されたセマンティクスを満たしている限り、ブレークを検知またはトリガーするために使用される具体的なメカニズムは、コンパイラおよび標準ライブラリの著者の裁量に委ねられます。
