инструментарий разработчикаdeveloper tooling

Утилиты отладки: std::breakpointDebugging Utilities: std::breakpoint

Небольшой набор функций в заголовке <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).

библиотечное добавление, не языковая фичаa library addition, not a language feature точное имя заголовка/API до ратификации может ещё измениться — см. секцию ссылокexact header/API naming may still shift before ratification — see the links section частый вопрос: чем breakpoint() отличается от breakpoint_if_debugging()common question: how breakpoint() differs from breakpoint_if_debugging()

Что этоWhat it is

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).

до C++26 — платформенный shim вручнуюbefore C++26 — a hand-rolled platform shim
#ifdef _WIN32
    #include <windows.h>
    #define TRAP() DebugBreak()
#else
    #include <csignal>
    #define TRAP() raise(SIGTRAP)
#endif

void on_fatal_error() { TRAP(); }
C++26 — переносимо, без макросовC++26 — portable, no macros
#include <debugging>

void on_fatal_error() { std::breakpoint(); }

Зачем ввелиWhy it was introduced

01

У каждой платформы был свой несовместимый способEvery platform had its own incompatible way to do this

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.

02

Каждая кроссплатформенная база писала и поддерживала свой шимEvery cross-platform codebase wrote and maintained its own shim

Любая кроссплатформенная кодовая база, которой было нужно "поставить паузу здесь, если под отладчиком", была вынуждена писать и поддерживать собственный небольшой слой абстракции платформы — движки, браузеры, обычные приложения независимо друг от друга изобретали один и тот же велосипед.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.

03

Давняя и на удивление распространённая дыра в стандартной библиотекеA long-standing and surprisingly common gap in the standard library

Стандартизация закрывает эту дыру раз и навсегда: именно такой крошечный платформенный шим встречается в бесчисленных кодовых базах (игровые движки, браузеры, прикладной код) исключительно потому, что стандартная библиотека раньше не предлагала ничего.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.

Как это работает под капотомHow it works under the hood

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();
}

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

Собственный assert-макрос с паузой вместо аварийного завершенияA custom assert macro that pauses instead of aborting

Нарушение внутреннего инварианта немедленно ставит на паузу под отладчиком, но безвредно ничего не делает во время автономного 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)
02

Отключение watchdog-таймаута во время пошаговой отладкиDisabling a watchdog timeout during single-stepping

Механизм 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();
}
03

Замена самодельного #ifdef-шимаReplacing a hand-rolled #ifdef shim

Существующий в кодовой базе #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();
04

Дополнительное логирование только при активной отладкеExtra logging only during an active debugging session

Подробная диагностика включается только когда отладчик подключён, чтобы не переполнять 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);
    }
}

ГраблиGotchas

std::breakpoint() опасен без подключённого отладчикаstd::breakpoint() is dangerous with no debugger attached

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.

breakpoint_if_debugging() — безопасный для production вариантbreakpoint_if_debugging() is the production-safe variant

В противоположность этому, 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.

is_debugger_present() — удобство разработки, не средство безопасностиis_debugger_present() is a dev convenience, not a security tool

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.