C++26 introduceert de nieuwe <debugging> header om een langlopend probleem op te lossen: het gebrek aan een gestandaardiseerde manier om programmatisch breakpoints te triggeren. Voorheen moesten ontwikkelaars vertrouwen op platform-specifieke hacks of externe libraries, wat leidde tot redundante code in grote projecten.
De header voegt drie noexcept en freestanding functies toe:
std::breakpoint(): Forceert een onvoorwaardelijk breakpoint. Zonder gekoppelde debugger is het gedrag platformafhankelijk.
std::isdebuggerpresent(): Controleert of het programma wordt gevolgd door een debugger. Deze functie is vervangbaar, wat essentieel is voor bare-metal systemen waar geen OS-ondersteuning is voor detectie.
std::breakpointifdebugging(): Een gemakfunctie die alleen een breakpoint triggert als er daadwerkelijk een debugger aanwezig is.
Naast breakpoints maakt de scheiding van deze functies het mogelijk om specifieke debug-logica te implementeren, zoals het uitschakelen van time-outs of het wisselen naar een debugger-vriendelijke allocator tijdens een actieve sessie.
C++26: de nieuwe debugging-header
#if defined(_MSC_VER)
__debugbreak();
#elif defined(__clang__)
__builtin_debugtrap();
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
__asm__ volatile("int3");
#else
raise(SIGTRAP);
#endif
Vrijwel elk groot C++-project vindt het wiel op deze manier opnieuw uit. Boost.Test heeft debuggerbreak(), Catch2 heeft CATCHBREAKINTODEBUGGER, Dear ImGui heeft IMDEBUGBREAK() en Unreal heeft UEDEBUGBREAK. De benodigde platformkennis is obscuur, maar bekend bij implementatie-experts — het was simpelweg niet gestandaardiseerd.
C++26 lost dit op met P2546R5 en het begeleidende P2810R4, dank aan Rene Ferdinand Rivera Morell. Een nieuwe <debugging> header biedt drie functies.
De drie functies
#include <debugging>
namespace std {
void breakpoint() noexcept;
bool is_debugger_present() noexcept;
void breakpoint_if_debugging() noexcept;
}
Alle drie de functies zijn noexcept en freestanding. Geen van hen is constexpr, aangezien debugging een runtime-aangelegenheid is.
std::breakpoint()
Dit is een onvoorwaardelijk breakpoint. Wanneer er een debugger is gekoppeld, stopt de uitvoering en wordt de controle overgedragen aan de debugger. Op x86 compileert dit doorgaans naar een enkele int3-instructie.
Wanneer er geen debugger aanwezig is, is het gedrag implementatie-afhankelijk. Op de meeste platforms ontvangt het proces een signaal (zoals SIGTRAP) en zal het beëindigen, tenzij het signaal wordt opgevangen.
std::isdebuggerpresent()
Deze functie geeft true terug als het programma momenteel wordt gevolgd door een debugger. De controle wordt bij elke aanroep direct uitgevoerd; het resultaat wordt niet gecachet, omdat een debugger op elk gewenst moment tijdens de uitvoering kan worden gekoppeld of losgekoppeld.
Hoe dit onder de motorkap werkt, verschilt per platform. Op Windows kan een implementatie bijvoorbeeld ::IsDebuggerPresent() aanroepen. Op Linux leest de libstdc++ van GCC het bestand /proc/self/status en controleert het veld TracerPid. De standaard schrijft geen specifiek mechanisme voor — de semantiek is implementatie-afhankelijk, waarbij het beoogde gedrag wordt beschreven in niet-normatieve notities.
Wat deze functie interessant maakt, is dat hij vervangbaar is, in dezelfde zin als operator new en operator delete. Je kunt je eigen versie definiëren:
bool std::is_debugger_present() noexcept {
return my_hardware_debug_flag;
}
Dit is geïntroduceerd door P2810R4 om het probleem met freestanding omgevingen op te lossen: op bare-metal platforms is er mogelijk geen manier op OS-niveau om een debugger te detecteren. Zonder vervangbaarheid zou de functie ofwel uitgesloten moeten worden van freestanding (wat overal #ifdef-guards zou vereisen) of een nutteloze standaardwaarde zouden moeten teruggeven. Vervangbaarheid stelt je in staat om de functie te koppelen aan wat jouw platform ook biedt.
std::breakpointifdebugging()
Dit is de gemaksfunctie waar de meeste gebruikers voor zullen kiezen. Het is equivalent aan:
if (std::is_debugger_present()) {
std::breakpoint();
}
Wanneer er een debugger is gekoppeld, raak je een breakpoint. Als dat niet zo is, gebeurt er niets. Dit is het patroon dat Catch2, Boost.Test en elk ander framework handmatig implementeert.
Meer dan breakpoints
Het feit dat isdebuggerpresent() een aparte functie is, maakt meer mogelijk dan alleen conditionele breakpoints. Je zou het kunnen gebruiken om:
- Extra diagnostische output te genereren tijdens het debuggen.
- Time-outs uit te schakelen die zouden afgaan tijdens een stepping-sessie.
- Over te schakelen naar een allocator die vriendelijker is voor debuggers.
- Optimalisaties over te slaan die debugging bemoeilijken.
De scheiding van verantwoordelijkheden is bewust: breakpoint() en isdebuggerpresent() zijn orthogonale bouwstenen, en breakpointifdebugging() is de gemakkelijke compositie hiervan.
Conclusie
<debugging> is een van die toevoegingen aan C++26 waarbij je je afvraagt waarom het zo lang heeft geduurd. De platformkennis is goed begrepen, elk groot project heeft al zijn eigen versie, en de API bestaat uit slechts drie functies. Soms zijn de meest nuttige toevoegingen aan de standaardbibliotheek juist de eenvoudigste.
C++26: de nieuwe debugging-header
#if defined(_MSC_VER)
__debugbreak();
#elif defined(__clang__)
__builtin_debugtrap();
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
__asm__ volatile("int3");
#else
raise(SIGTRAP);
#endif
Vrijwel elk groot C++-project vindt het wiel op deze manier opnieuw uit. Boost.Test heeft debuggerbreak(), Catch2 heeft CATCHBREAKINTODEBUGGER, Dear ImGui heeft IMDEBUGBREAK() en Unreal heeft UEDEBUGBREAK. De benodigde platformkennis is obscuur, maar bekend bij implementatie-experts — het was simpelweg niet gestandaardiseerd.
C++26 lost dit op met P2546R5 en het begeleidende P2810R4, dank aan Rene Ferdinand Rivera Morell. Een nieuwe <debugging> header biedt drie functies.
De drie functies
#include <debugging>
namespace std {
void breakpoint() noexcept;
bool is_debugger_present() noexcept;
void breakpoint_if_debugging() noexcept;
}
Alle drie de functies zijn noexcept en freestanding. Geen van hen is constexpr, aangezien debugging een runtime-aangelegenheid is.
std::breakpoint()
Dit is een onvoorwaardelijk breakpoint. Wanneer er een debugger is gekoppeld, stopt de uitvoering en wordt de controle overgedragen aan de debugger. Op x86 compileert dit doorgaans naar een enkele int3-instructie.
Wanneer er geen debugger aanwezig is, is het gedrag implementatie-afhankelijk. Op de meeste platforms ontvangt het proces een signaal (zoals SIGTRAP) en zal het beëindigen, tenzij het signaal wordt opgevangen.
std::isdebuggerpresent()
Deze functie geeft true terug als het programma momenteel wordt gevolgd door een debugger. De controle wordt bij elke aanroep direct uitgevoerd; het resultaat wordt niet gecachet, omdat een debugger op elk gewenst moment tijdens de uitvoering kan worden gekoppeld of losgekoppeld.
Hoe dit onder de motorkap werkt, verschilt per platform. Op Windows kan een implementatie bijvoorbeeld ::IsDebuggerPresent() aanroepen. Op Linux leest de libstdc++ van GCC het bestand /proc/self/status en controleert het veld TracerPid. De standaard schrijft geen specifiek mechanisme voor — de semantiek is implementatie-afhankelijk, waarbij het beoogde gedrag wordt beschreven in niet-normatieve notities.
Wat deze functie interessant maakt, is dat hij vervangbaar is, in dezelfde zin als operator new en operator delete. Je kunt je eigen versie definiëren:
bool std::is_debugger_present() noexcept {
return my_hardware_debug_flag;
}
Dit is geïntroduceerd door P2810R4 om het probleem met freestanding omgevingen op te lossen: op bare-metal platforms is er mogelijk geen manier op OS-niveau om een debugger te detecteren. Zonder vervangbaarheid zou de functie ofwel uitgesloten moeten worden van freestanding (wat overal #ifdef-guards zou vereisen) of een nutteloze standaardwaarde zouden moeten teruggeven. Vervangbaarheid stelt je in staat om de functie te koppelen aan wat jouw platform ook biedt.
std::breakpointifdebugging()
Dit is de gemaksfunctie waar de meeste gebruikers voor zullen kiezen. Het is equivalent aan:
if (std::is_debugger_present()) {
std::breakpoint();
}
Wanneer er een debugger is gekoppeld, raak je een breakpoint. Als dat niet zo is, gebeurt er niets. Dit is het patroon dat Catch2, Boost.Test en elk ander framework handmatig implementeert.
Meer dan breakpoints
Het feit dat isdebuggerpresent() een aparte functie is, maakt meer mogelijk dan alleen conditionele breakpoints. Je zou het kunnen gebruiken om:
- Extra diagnostische output te genereren tijdens het debuggen.
- Time-outs uit te schakelen die zouden afgaan tijdens een stepping-sessie.
- Over te schakelen naar een allocator die vriendelijker is voor debuggers.
- Optimalisaties over te slaan die debugging bemoeilijken.
De scheiding van verantwoordelijkheden is bewust: breakpoint() en isdebuggerpresent() zijn orthogonale bouwstenen, en breakpointifdebugging() is de gemakkelijke compositie hiervan.
Conclusie
<debugging> is een van die toevoegingen aan C++26 waarbij je je afvraagt waarom het zo lang heeft geduurd. De platformkennis is goed begrepen, elk groot project heeft al zijn eigen versie, en de API bestaat uit slechts drie functies. Soms zijn de meest nuttige toevoegingen aan de standaardbibliotheek juist de eenvoudigste.