В C++26 появится заголовок <debugging>: программные точки останова
Сандор Дарго в своём блоге разбирает новый заголовок
Новый заголовок даёт три функции. Все они 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 даёт три функции: 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