Principe du durcissement

Le standard C++26 ajoute la notion de implementation hardened. Dans une implémentation durcie, chaque précondition critique – par exemple l’accès hors limites d’un std::vector via operator[] – déclenche une violation de contrat plutôt qu’un comportement indéfini. La spécification transforme ainsi certaines préconditions existantes en hardened preconditions qui, lorsqu’elles échouent, entraînent un arrêt du programme avec une sémantique de terminaison. Cette approche s’appuie sur le nouveau modèle de Contracts introduit dans C++26, où les modes ignore, observe, enforce et quick‑enforce existent, mais le durcissement impose obligatoirement le mode enforce avec terminaison.

Les propositions P3471, P3697 et P3878 constituent le socle documentaire : P3471 décrit le cadre général, P3697 ajoute des cas spécifiques (par ex. basic_stacktrace, shared_ptr), et P3878 insiste sur l’obligation d’une sémantique terminante. Les critères de sélection des fonctions concernées sont clairement définis – la violation doit entraîner un problème de sécurité mémoire, le site d’appel doit disposer de toutes les données nécessaires, et la vérification doit être réalisable en temps constant avec un coût marginal.

Mécanisme d’implémentation et macros

Le durcissement reste implementation‑defined. Les principaux fournisseurs de compilateurs exposent des macros distinctes :

# GCC / libstdc++
#define _GLIBCXX_ASSERTIONS          // active les vérifications légères
// -fhardened active le mode complet en mode optimisé

# Clang / libc++
#define _LIBCPP_HARDENING_MODE NONE|FAST|EXTENSIVE|DEBUG

# MSVC STL
#define _MSVC_STL_HARDENING=1          // activation globale
#define _MSVC_STL_HARDENING_VECTOR    // contrôle fin sur std::vector

Sur GCC 16.1, les assertions sont déjà activées par défaut en mode non optimisé, ce qui explique pourquoi l’exemple ci‑dessous échoue même sans -D_GLIBCXX_ASSERTIONS. En mode optimisé (-O2), les vérifications sont désactivées à moins d’ajouter explicitement la macro. Cette différence illustre le compromis entre performance et sécurité : les vérifications coûtent quelques cycles, mais elles éliminent le risque de débordement non détecté qui pourrait conduire à une corruption de mémoire ou à une exploitation.

Exemple d’exécution et limites

L’exemple suivant montre le comportement avec et sans durcissement :

#include <vector>
#include <iostream>
int main(){
    std::vector<int> v{1,2,3};
    int idx; std::cin>>idx; v[idx]=idx;
    std::cout<<"hello";
}

Compilé avec g++ -std=c++26 -O0 et une entrée 100000, le programme s’arrête avec le message d’assertion « __n < this->size() » et un signal SIGSEGV. En mode -O2 sans macro, le même code aboutit à un comportement indéfini, souvent une violation d’accès non détectée. En ajoutant -D_GLIBCXX_ASSERTIONS, le même arrêt se produit, confirmant que le mode durci fonctionne de façon identique en optimisation.

Malgré ces garanties, le durcissement ne rend pas C++ « pleinement sûr ». Les vérifications ne couvrent que les préconditions qui remplissent les trois critères cités ; de nombreuses fonctions restent non protégées. De plus, la dépendance aux macros signifie que la portabilité entre environnements nécessite une configuration explicite, et les implémentations peuvent choisir des stratégies d’instrumentation différentes, ce qui rend difficile une comparaison de performances uniforme.