Historically, C++ developers have had to manually implement platform-specific logic to trigger a debugger breakpoint. Because different operating systems and architectures handle debugging signals differently, projects like Boost.Test, Catch2, and Unreal Engine have each created their own internal macros (e.g., `__debugbreak()` for MSVC or `__builtin_debugtrap()` for Clang). This lack of standardization forced developers to maintain 'arcane' platform knowledge to ensure a consistent debugging experience across different environments.
C++26 addresses this by introducing the `<debugging>` header, which provides three standardized functions in the `std` namespace: `breakpoint()`, `is_debugger_present()`, and `breakpoint_if_debugging()`. These functions are designed to be 'freestanding,' meaning they can be used in environments without an operating system, such as bare-metal embedded systems. To ensure they work in these contexts, `is_debugger_present()` is 'replaceable'—similar to `operator new`—allowing developers to define their own implementation for specific hardware where standard OS-level detection is unavailable.
While `breakpoint()` provides an unconditional halt and `is_debugger_present()` returns a boolean status, the most common use case is handled by `breakpoint_if_debugging()`. This convenience function combines the two, only triggering a halt if a debugger is actively tracing the process. Beyond simple breakpoints, the separation of these functions allows developers to use `is_debugger_present()` for other diagnostic tasks, such as switching to a more debugger-friendly memory allocator, disabling timeouts during stepping sessions, or producing extra diagnostic output. However, implementation details remain platform-dependent; for example, on Linux, the standard may rely on reading `/proc/self/status`, while on Windows, it may wrap the `IsDebuggerPresent()` API.
One technical nuance involves how these functions interact with hardware instructions. On x86 architectures, `breakpoint()` is expected to compile to a single `int3` instruction. Some observers have noted that the effectiveness of this approach depends on the debugger's ability to correctly interpret the instruction pointer at the moment of the trap. Because the standard defines the behavior of these functions as 'implementation-defined,' the specific mechanism used to detect or trigger a break remains at the discretion of the compiler and standard library authors, provided they meet the specified semantics.
