В C++26 появится заголовок <debugging>: программные точки останова

Сандор Дарго в своём блоге разбирает новый заголовок стандарта C++26. Раньше, чтобы программно остановиться в отладчике, каждый крупный C++-проект писал собственную обёртку: у Boost.Test это debugger_break(), у Catch2, CATCH_BREAK_INTO_DEBUGGER, у Dear ImGui, IM_DEBUG_BREAK(), у Unreal, UE_DEBUG_BREAK. Платформенные знания для этого, по словам автора, непростые, но хорошо известны разработчикам инструментов, их просто не стандартизировали. В C++26 это исправляют предложения P2546R5 и сопутствующее P2810R4; автор благодарит за них Rene Ferdinand Rivera Morell.

Новый заголовок даёт три функции. Все они noexcept и freestanding (доступны и в автономных средах без полной стандартной библиотеки), ни одна не constexpr, потому что отладка, вопрос времени выполнения.

std::breakpoint(), безусловная точка останова. Если отладчик подключён, выполнение останавливается и управление переходит к нему; на x86 это обычно одна инструкция int3. Если отладчика нет, поведение определяется реализацией: на большинстве платформ процесс получает сигнал (например, SIGTRAP) и завершается, если сигнал не перехвачен.

std::is_debugger_present() возвращает true, если программа сейчас отслеживается отладчиком. Проверка выполняется при каждом вызове и не кэшируется, поскольку отладчик может подключиться или отключиться в любой момент. Механизм зависит от платформы: в Windows реализация может вызывать ::IsDebuggerPresent(), в Linux libstdc++ из GCC читает /proc/self/status и проверяет поле TracerPid. Стандарт механизм не предписывает: семантика определяется реализацией, а задуманное поведение описано в ненормативных примечаниях. Функция заменяемая, так же, как operator new и operator delete, то есть пользователь может определить собственную версию. Заменяемость внесло P2810R4 ради проблемы freestanding: на «голом железе» может не быть способа определить отладчик средствами ОС. Без замены функцию пришлось бы исключить из freestanding (и тогда везде понадобились бы #ifdef) либо возвращать бесполезное значение по умолчанию. Заменяемость позволяет привязать её к тому, что даёт платформа.

std::breakpoint_if_debugging(), удобная функция, которой, по мнению автора, будет пользоваться большинство: если отладчик подключён, срабатывает точка останова, если нет, ничего не происходит. Именно такой шаблон вручную реализуют Catch2, Boost.Test и другие фреймворки.

Отдельная is_debugger_present() нужна не только для условных остановок. Автор перечисляет, как её можно использовать: выводить дополнительную диагностику во время отладки, отключать тайм-ауты, которые срабатывали бы при пошаговом выполнении, переключаться на более удобный для отладки аллокатор, пропускать оптимизации, мешающие отладке. Разделение ответственности, по его словам, намеренное: breakpoint() и is_debugger_present(), независимые строительные блоки, а breakpoint_if_debugging(), их удобная композиция.

Вывод автора: , ещё одно дополнение C++26, из-за которого удивляешься, почему на него ушло столько времени: знания о платформах давно известны, у каждого крупного проекта уже есть своя версия, а API, три функции. Иногда самые полезные дополнения стандартной библиотеки, самые простые.

Ключевые факты

  • Заголовок в C++26 даёт три функции: std::breakpoint(), std::is_debugger_present() и std::breakpoint_if_debugging(); описаны в P2546R5 и P2810R4.
  • Все три функции noexcept и freestanding, но не constexpr: отладка, вопрос времени выполнения.
  • std::breakpoint() без подключённого отладчика ведёт себя по правилам реализации: на большинстве платформ процесс получает сигнал вроде SIGTRAP и завершается, если сигнал не перехвачен.
  • std::is_debugger_present() не кэширует результат и заменяема, как operator new и operator delete, это решает проблему freestanding на «голом железе».
  • Сейчас у каждого крупного проекта свой макрос: debugger_break() в Boost.Test, CATCH_BREAK_INTO_DEBUGGER в Catch2, IM_DEBUG_BREAK() в Dear ImGui, UE_DEBUG_BREAK в Unreal.

Почему это важно

Программная точка останова, давняя потребность, которую каждый крупный проект закрывал своим макросом с платформенными тонкостями. Стандартизация убирает это дублирование: появляется единый переносимый способ остановиться в отладчике и узнать, подключён ли он. Автор считает, что это очередное простое, но полезное дополнение стандартной библиотеки.

Кому это важно

Прежде всего C++-разработчикам и авторам тестовых фреймворков и библиотек, которые сейчас держат собственные обёртки для остановки в отладчике. Отдельно, тем, кто пишет код для встраиваемых систем и «голого железа»: для них заменяемость is_debugger_present() решает проблему отсутствия средства определения отладчика на уровне ОС. Тема узкоспециальная и напрямую не связана с ИИ.

Как это применить

Когда заголовок станет доступен в вашем компиляторе и стандартной библиотеке, самодельные макросы можно заменить на std::breakpoint_if_debugging() для условной остановки, а is_debugger_present() использовать для режимов отладки: дополнительной диагностики, отключения тайм-аутов, другого аллокатора, пропуска оптимизаций. На платформах без способа обнаружить отладчик свою версию is_debugger_present() можно определить самостоятельно. Какие компиляторы уже поставляют заголовок, в посте не названо; упомянут лишь механизм в libstdc++ из GCC (чтение TracerPid в /proc/self/status).

Можно ли доверять

Это авторский блог-пост с разбором, а не текст стандарта: детали берите из самих предложений P2546R5 и P2810R4. Описание функций согласуется с приведёнными в нём номерами предложений, а выводы вроде «простые дополнения полезнее всего», мнение автора. В тексте, на котором основан пересказ, нет примеров кода, поэтому конкретные сигнатуры и определения стоит сверять с первоисточником.

Риски и подводные камни

Без подключённого отладчика std::breakpoint() может завершить процесс сигналом, если его не перехватить, вызывать её безусловно в рабочем коде опасно. Семантика is_debugger_present() определяется реализацией, а результат не кэшируется и может меняться между вызовами, поэтому на него нельзя полагаться как на постоянное свойство запуска. Функции не constexpr, а условия вроде отключения тайм-аутов по наличию отладчика меняют поведение программы под отладкой и без неё.

«Иногда самые полезные дополнения стандартной библиотеки, самые простые.»

— автор поста в блоге sandordargo.com