C++26: The New Debugging Header
If you’ve ever needed to hit a breakpoint programmatically in C++, you’ve probably written something like this:
以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。
If you’ve ever needed to hit a breakpoint programmatically in C++, you’ve probably written something like this:
1 2 3 4 5 6 7 8 9
#if defined(_MSC_VER) __debugbreak(); #elif defined(clang) __builtin_debugtrap(); #elif defined(GNUC) && (defined(i386) || defined(x86_64)) asm volatile("int3"); #else raise(SIGTRAP); #endif
Every major C++ project reinvents this wheel. Boost.Test has debugger_break(), Catch2 has CATCH_BREAK_INTO_DEBUGGER, Dear ImGui has IM_DEBUG_BREAK() and Unreal has UE_DEBUG_BREAK. The platform knowledge required is arcane but well-known to tooling implementers — it just wasn’t standardized.
C++26 fixes this with P2546R5 and its companion P2810R4 thanks to Rene Ferdinand Rivera Morell. A new <debugging> header provides three functions.
1 2 3 4 5 6 7
#include <debugging>
namespace std { void breakpoint() noexcept; bool is_debugger_present() noexcept; void breakpoint_if_debugging() noexcept; }
All three are noexcept and all three are freestanding. None are constexpr — debugging is a runtime concern.
An unconditional breakpoint. When a debugger is attached, execution halts and control is handed to the debugger. On x86, this typically compiles to a single int3 instruction.
When no debugger is present, the behaviour is implementation-defined — on most platforms the process receives a signal (such as SIGTRAP) and will terminate unless the signal is caught.
Returns true if the program is currently being traced by a debugger. The check is performed immediately on each call — the result is not cached because a debugger can attach or detach at any time during execution.
How this works under the hood varies by platform. On Windows, for example, an implementation might call ::IsDebuggerPresent(). On Linux, GCC’s libstdc++ reads /proc/self/status and checks the TracerPid field. The standard doesn’t mandate any particular mechanism — the semantics are implementation-defined, with the intended behaviour described in non-normative notes.
What makes this function interesting is that it’s replaceable, in the same sense as operator new and operator delete. You can define your own version:
1 2 3
bool std::is_debugger_present() noexcept { return my_hardware_debug_flag; }
This was introduced by P2810R4 to solve the freestanding problem: on bare-metal platforms, there may be no OS-level way to detect a debugger. Without replaceability, the function would either have to be excluded from freestanding (forcing #ifdef guards everywhere) or return a useless default. Replaceability lets you wire it to whatever your platform provides.
The convenience function most users will reach for. It’s equivalent to:
1 2
if (std::is_debugger_present()) std::breakpoint();
When a debugger is attached, you hit a breakpoint. When it’s not, nothing happens. This is the pattern that Catch2, Boost.Test, and every other framework implements manually.
Having is_debugger_present() as a separate function enables more than just conditional breakpoints. You might use it to:
The separation of concerns is deliberate — breakpoint() and is_debugger_present() are orthogonal building blocks, and breakpoint_if_debugging() is the convenience composition.
<debugging> is another one of those C++26 additions that makes you wonder why it took this long. The platform knowledge is well-understood, every major project already has its own version, and the API is three functions. Sometimes the most useful standard library additions are the simplest ones.
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。