Contexte et besoin
Jusqu’à présent, chaque bibliothèque C++ majeure a implémenté son propre mécanisme de rupture d’exécution : Boost.Test propose debugger_break(), Catch2 expose CATCH_BREAK_INTO_DEBUGGER, Dear ImGui utilise IM_DEBUG_BREAK() et Unreal offre UE_DEBUG_BREAK. Ces solutions reposent sur une connaissance spécifique du système d’exploitation et du processeur, ce qui rend leur portabilité difficile et oblige les développeurs à reproduire du code identique dans chaque projet.
Le comité du standard C++ a résolu ce problème avec les propositions P2546R5 et P2810R4, intégrées dans C++26 sous la forme d’un nouvel en‑tête <debugging>. L’objectif est de fournir une API unique, fiable et utilisable même dans les environnements freestanding.
Fonctions introduites par
Trois fonctions sont définies, toutes noexcept et freestanding. Aucune n’est constexpr, car le débogage dépend du runtime.
std::breakpoint();std::breakpoint() déclenche inconditionnellement un point d’arrêt. Sur les architectures x86, le compilateur le traduit généralement en l’instruction int3. En l’absence de débogueur, le comportement est implementation‑defined : la plupart des systèmes envoient un signal SIGTRAP, ce qui entraîne la terminaison du processus si le signal n’est pas intercepté.
if (std::is_debugger_present()) std::breakpoint();std::is_debugger_present() renvoie true lorsqu’un débogueur trace le processus. L’interrogation est effectuée à chaque appel, sans mise en cache, afin de refléter les attachements ou détachements dynamiques. Sous Windows, l’implémentation typique invoque ::IsDebuggerPresent(). Sous Linux, la bibliothèque libstdc++ lit /proc/self/status et examine le champ TracerPid. Le standard ne fixe aucun mécanisme précis ; les exigences sont décrites dans des notes non normatives.
std::breakpoint_if_debugging() combine les deux précédentes : il agit comme std::breakpoint() uniquement si std::is_debugger_present() renvoie true. Cette fonction reproduit le motif déjà présent dans Boost, Catch2 et d’autres frameworks, mais sans duplication de code.
Mécanisme d’implémentation et remplaçabilité
La fonction std::is_debugger_present() est déclarée replaceable, à l’instar des opérateurs new et delete. Cette caractéristique répond à un problème crucial pour les plateformes « bare‑metal » : l’absence d’un service d’OS capable de signaler la présence d’un débogueur. En autorisant une surcharge, les développeurs peuvent fournir une implémentation adaptée à leur environnement matériel, évitant ainsi des #ifdef dispersés dans le code.
Le caractère freestanding des trois fonctions garantit leur disponibilité dans les contextes où la bibliothèque standard complète n’est pas liée, comme les microcontrôleurs ou les environnements d’exécution très limités. Cette contrainte renforce la portabilité du code de diagnostic.
Implications et limites
La séparation de std::breakpoint() et std::is_debugger_present() crée deux blocs de construction orthogonaux. Au‑delà des points d’arrêt conditionnels, is_debugger_present() peut piloter des comportements spécifiques : génération d’informations de diagnostic supplémentaires, désactivation de temporisations qui pourraient interrompre une session de pas à pas, sélection d’un allocateur optimisé pour le débogage ou désactivation d’optimisations agressives qui compliquent l’inspection du code.
Le principal point d’incertitude réside dans le comportement lorsque aucun débogueur n’est présent. Comme la norme laisse le résultat à l’implémentation, les développeurs doivent anticiper la réception possible d’un SIGTRAP ou d’une autre forme de terminaison. De plus, la fonction n’est pas constexpr, ce qui exclut son utilisation dans les contextes d’évaluation à la compilation.
En définitive, l’en‑tête <debugging> de C++26 fournit une abstraction standardisée, éliminant la duplication de code et facilitant le déploiement de diagnostics cohérents sur l’ensemble des plateformes supportées, tout en conservant la flexibilité requise pour les environnements très contraints.