инструментарий разработчикаdeveloper tooling
Небольшой набор функций в заголовке <debugging> для программного взаимодействия с отладчиком: std::breakpoint(), std::breakpoint_if_debugging() и std::is_debugger_present() — переносимая замена платформенным хакам вроде DebugBreak()/raise(SIGTRAP).A small set of functions in the <debugging> header for interacting with a debugger programmatically: std::breakpoint(), std::breakpoint_if_debugging(), and std::is_debugger_present() — a portable replacement for platform-specific hacks like DebugBreak()/raise(SIGTRAP).
P2546 добавляет заголовок <debugging> с тремя функциями. std::breakpoint() безусловно инициирует программный breakpoint (останавливает выполнение под отладчиком — аналог __debugbreak() в MSVC или сигнала SIGTRAP, но переносимо). std::breakpoint_if_debugging() делает то же самое, но только если отладчик реально подключён (иначе — no-op, безопасно оставлять в коде, который может запускаться без отладчика). std::is_debugger_present() возвращает, подключён ли отладчик к процессу прямо сейчас — для условного изменения поведения (например, отключения таймаут-проверки при пошаговой отладке).P2546 adds a <debugging> header with three functions. std::breakpoint() unconditionally triggers a programmatic breakpoint (halting execution under a debugger — similar to MSVC's __debugbreak() or raising SIGTRAP, but portably). std::breakpoint_if_debugging() does the same, but only if a debugger is actually attached (a no-op otherwise, safe to leave in code that might run without a debugger). std::is_debugger_present() returns whether a debugger is currently attached to the process — for conditionally changing behavior (e.g. disabling a timeout check while single-stepping).
#ifdef _WIN32
#include <windows.h>
#define TRAP() DebugBreak()
#else
#include <csignal>
#define TRAP() raise(SIGTRAP)
#endif
void on_fatal_error() { TRAP(); }
#include <debugging>
void on_fatal_error() { std::breakpoint(); }
Windows: DebugBreak()/IsDebuggerPresent(), вызов WinAPI. POSIX-подобные системы: raise(SIGTRAP) или хрупкое, медленное обнаружение через ptrace, которое обычно требует форкать дочерний процесс для проверки. Компиляторы: __builtin_trap() (GCC/Clang), который реально завершает процесс, а не просто ставит на паузу под отладчиком, — существенная семантическая разница с настоящим breakpoint.Windows: DebugBreak()/IsDebuggerPresent(), a WinAPI call. POSIX-ish systems: raise(SIGTRAP) or fragile, slow ptrace-based detection that typically requires forking a child process to check. Compilers: __builtin_trap() (GCC/Clang), which actually aborts rather than pausing under a debugger — a real semantic difference from a breakpoint.
Любая кроссплатформенная кодовая база, которой было нужно "поставить паузу здесь, если под отладчиком", была вынуждена писать и поддерживать собственный небольшой слой абстракции платформы — движки, браузеры, обычные приложения независимо друг от друга изобретали один и тот же велосипед.Any cross-platform codebase wanting "pause here if being debugged" had to write and maintain its own small platform-abstraction shim — engines, browsers, and ordinary applications independently reinvented the same wheel.
Стандартизация закрывает эту дыру раз и навсегда: именно такой крошечный платформенный шим встречается в бесчисленных кодовых базах (игровые движки, браузеры, прикладной код) исключительно потому, что стандартная библиотека раньше не предлагала ничего.Standardizing this closes the gap once and for all: this exact kind of tiny platform shim exists in countless codebases (game engines, browsers, general application code) purely because the standard library offered nothing.
std::breakpoint() вызывается безусловно в редком, но важном пути обработки ошибок во время разработки; std::breakpoint_if_debugging() ставится защитно в горячей assert-подобной проверке, которая должна молчать в CI/production, но немедленно ставить на паузу при активной отладке разработчиком; std::is_debugger_present() используется для условного изменения поведения — например, пропуска искусственного таймаута.std::breakpoint() is called unconditionally in a rare but important error path during development; std::breakpoint_if_debugging() is placed defensively in a hot assertion-like check that should stay silent in CI/production but pause immediately when a developer is actively debugging; std::is_debugger_present() is used to conditionally change behavior — for instance, skipping an artificial timeout.
#include <debugging>
void handle_corrupt_state() {
std::breakpoint(); // безусловно — только в редком, критическом пути разработки// unconditional — only on a rare, critical development-time path
}
void check_invariant(bool ok) {
if (!ok) {
std::breakpoint_if_debugging(); // no-op в CI, пауза при живой отладке// no-op in CI, pauses during a live debugging session
}
}
void wait_for_response() {
if (std::is_debugger_present()) {
return; // пропускаем искусственный таймаут во время пошаговой отладки// skip the artificial timeout while single-stepping
}
enforce_timeout();
}
Нарушение внутреннего инварианта немедленно ставит на паузу под отладчиком, но безвредно ничего не делает во время автономного CI-прогона — в отличие от обычного assert(), который аварийно завершает процесс независимо от контекста.Failing an internal invariant pauses immediately under a debugger, but harmlessly no-ops during an unattended CI run — in contrast with a plain assert(), which always aborts regardless of context.
#define MY_ASSERT(cond) \
do { if (!(cond)) std::breakpoint_if_debugging(); } while (0)
Механизм watchdog, который иначе убивал бы код за "слишком долгое" выполнение, отключается, пока разработчик вручную проходит код шаг за шагом под отладчиком.A watchdog mechanism that would otherwise kill code for taking "too long" is disabled while a developer manually single-steps through it under a debugger.
void run_task_with_watchdog() {
if (!std::is_debugger_present()) {
arm_watchdog(std::chrono::seconds(5));
}
run_task();
}
Существующий в кодовой базе #ifdef _WIN32 ... DebugBreak() ... #else ... raise(SIGTRAP) ... #endif заменяется одной переносимой строкой — упрощение видно наглядно бок о бок.A codebase's existing #ifdef _WIN32 ... DebugBreak() ... #else ... raise(SIGTRAP) ... #endif is replaced with one portable line — the simplification is visible side by side.
// до: платформенный макрос, который нужно поддерживать самостоятельно// before: a platform macro that had to be maintained in-house
#ifdef _WIN32
#define APP_TRAP() DebugBreak()
#else
#define APP_TRAP() raise(SIGTRAP)
#endif
// после: одна переносимая функция стандартной библиотеки// after: one portable standard library function
std::breakpoint();
Подробная диагностика включается только когда отладчик подключён, чтобы не переполнять production-логи, но давать максимум детали в интерактивной сессии отладки.Verbose diagnostics are enabled only when a debugger is attached, to avoid flooding production logs while still getting rich detail during an interactive debugging session.
void log_request(const Request& req) {
if (std::is_debugger_present()) {
log_verbose(req);
} else {
log_summary(req);
}
}
std::breakpoint() безусловно ловит трап ДАЖЕ КОГДА ОТЛАДЧИК НЕ ПОДКЛЮЧЁН — поведение в этом случае platform-defined (обычно поднимается сигнал, который без отладчика, готового его перехватить, завершает процесс, аналогично необработанному SIGTRAP). Небезопасно разбрасывать безусловный std::breakpoint() по путям кода, которые могут выполняться в production без отладчика.std::breakpoint() unconditionally traps EVEN WHEN NO DEBUGGER IS ATTACHED — behavior in that case is platform-defined (commonly it raises a signal that, with no debugger to catch it, terminates the process, similar to an unhandled SIGTRAP). It is NOT safe to sprinkle the unconditional std::breakpoint() into code paths that might run in production without a debugger.
В противоположность этому, std::breakpoint_if_debugging() безопасен для оставления в поставляемом коде именно потому, что молча ничего не делает при отсутствии отладчика. Перепутать, какая из двух функций безопасна оставлять в production, — самая важная путаница в этой фиче.Conversely, std::breakpoint_if_debugging() is safe to leave in shipped code specifically because it no-ops with no debugger attached. Mixing up which of the two functions is safe to leave in shipped code is the single most important distinction in this feature.
std::is_debugger_present() может иметь нетривиальную стоимость или платформенные оговорки, а некоторые механизмы обнаружения известны как обходимые/подделываемые и не задумывались как anti-debugging или security-контроль. Это удобство workflow разработки, а не механизм защиты от вмешательства — полагаться на него в этом качестве не стоит.std::is_debugger_present() can have nontrivial cost or platform-specific caveats, and some detection mechanisms are known to be defeatable/spoofable and were never intended as an anti-debugging or security control. This is a development-workflow convenience feature, not a security or anti-tampering mechanism, and should never be relied upon as one.