Pendant des décennies, le C a régné en maître comme langage par défaut du firmware de microcontrôleurs et des systèmes d’exploitation temps réel (RTOS) comme FreeRTOS. Le discours historique était simple : C++ ajouterait du poids à l’exécution, un surcoût de performance non déterministe et du brassage mémoire.
Or, dans l’ingénierie embarquée moderne—des systèmes automobiles critiques (ISO 26262) aux dispositifs médicaux et à l’IoT—C++17 s’est imposé comme le sweet spot.
C++17 réalise ce que C ne peut pas faire : des abstractions à coût zéro. Il permet aux architectes firmware d’écrire du code plus sûr et plus expressif, compilé en un assembleur aussi compact et rapide que du C écrit à la main, sans sacrifier le contrôle bas niveau des registres.
Voici pourquoi C++17 devient rapidement le standard de l’industrie en architecture logicielle embarquée.
1. Abstractions à coût zéro : la sûreté de typage sans surcoût mémoire
En environnement bare-metal ou RTOS, l’allocation dynamique sur le tas (malloc/free ou new/delete) est fortement déconseillée, voire interdite, à cause de la fragmentation du tas et des temps d’exécution non déterministes.
C++17 résout ce problème avec des wrappers de types sans surcoût qui éliminent les bugs de pointeurs et les valeurs sentinelle :
- std::optional (fini les valeurs sentinelle) : au lieu de nombres magiques comme -1 ou de pointeurs NULL pour signaler une défaillance capteur,
std::optional<T>encapsule explicitement un résultat optionnel sur la pile, sans allocation sur le tas. - std::variant (unions étiquetées typées) : remplace les casts d’union C dangereux pour les messages de protocole, les machines à états et les files d’événements. Il élimine les pointeurs void bruts sans fonctions virtuelles ni surcoût de dispatch dynamique.
- std::string_view (références de chaînes non propriétaires) : permet de passer des buffers de caractères ou de parser des commandes AT/télémétrie sans copie mémoire ni allocation de chaînes.
2. Métaprogrammation à la compilation avec if constexpr
En C embarqué traditionnel, cibler plusieurs révisions matérielles ou options de périphériques exige des chaînes de #ifdef complexes. Cela pollue le code, nuit à la maintenabilité et complique l’analyse statique.
Le if constexpr de C++17 évalue les branchements conditionnels entièrement à la compilation :
Les branches non retenues sont entièrement écartées par le compilateur. Résultat : zéro instruction de branchement dans le binaire final.
3. Déplacer les calculs d’exécution vers la mémoire Flash (constexpr)
L’efficacité énergétique et le temps d’exécution pire cas (WCET) déterministe dépendent de la minimisation des cycles d’horloge CPU actifs. C++17 étend considérablement les capacités de constexpr.
Tables de correspondance CRC, tableaux mathématiques et calculs complexes de masques de registres peuvent être évalués pendant la compilation.
Au lieu d’exécuter des boucles d’initialisation au démarrage, les tables de données pré-calculées sont gravées directement dans la Flash du MCU (.rodata), libérant du temps d’exécution et de la RAM précieuse.
4. Des API plus expressives : les structured bindings
Les types de retour complexes des pilotes (codes de statut associés à des données utiles) exigeaient traditionnellement en C le passage de pointeurs ou de références mutables :
CODE_C_OLDC++17 introduit les structured bindings, qui simplifient la gestion des retours et améliorent la lisibilité :
CODE_SB5. Support universel des toolchains & compilateurs
Pourquoi C++17 plutôt que C++20 ou C++23 ?
Si C++20 apporte des fonctionnalités remarquables (comme std::span et les Concepts), C++17 reste le standard universel et sûr :
- Maturité inter-toolchains : GCC, Clang, Keil MDK, IAR Embedded Workbench et les compilateurs Texas Instruments offrent tous un support C++17 mature et certifié pour ARM Cortex-M, RISC-V et ESP32.
- Alignement MISRA & AUTOSAR : les outils d’analyse statique (comme CppDepend, PC-lint et Clang-Tidy) disposent de jeux de règles C++17 complets, adaptés à la conformité safety embarquée.
6. Gestion déterministe des ressources via RAII (ni locks pendus ni fuites)
En développement RTOS en C, quitter prématurément une fonction (early return ou vérification d’erreur) impose un nettoyage méticuleux : libérer les mutex, désactiver les interruptions, libérer les buffers. Un seul xSemaphoreGive() oublié, et c’est le deadlock.
C++17 exploite Resource Acquisition Is Initialization (RAII) pour rendre le nettoyage automatique et déterministe :
- std::lock_guard / std::scoped_lock : déverrouille automatiquement les mutex RTOS ou sections critiques à la sortie de scope de l’objet, quelle que soit la façon dont la fonction se termine.
- Coût d’exécution nul : les appels de destructeurs RAII sont inlinés par le compilateur aux points de sortie de la fonction et produisent un assembleur identique au nettoyage C manuel—sans risque d’erreur humaine.
7. Attributs de sécurité à la compilation ([[nodiscard]] et [[maybe_unused]])
Ignorer les codes de retour (statuts d’erreur des pilotes HAL ou échecs d’envoi de files RTOS) est l’une des principales causes de crashs de firmware difficiles à déboguer.
C++17 introduit des attributs standard qui imposent la sécurité matérielle directement dans la pipeline de build :
- [[nodiscard]] : force le compilateur à émettre un avertissement si le code de retour d’une fonction est ignoré (ex.
[[nodiscard]] HardwareStatus readADC()). - [[maybe_unused]] : marque explicitement les paramètres de callback inutilisés ou les variables de debug compilées conditionnellement, gardant les builds
-Werrorpropres sans les hacks de cast(void)var;.
8. Abstraction matérielle typée via enum class
Les enum C standard fuient leur portée dans le namespace global et se convertissent implicitement en entiers, source de bugs logiques subtils (comme assigner un enum de baud rate UART à un index de canal Timer).
L’enum class à portée de C++17, associé à un type sous-jacent explicite, corrige cela à coût d’exécution nul :
Spécifier : uint8_t garantit que le compilateur compacte l’enum sur un seul octet au lieu d’un entier 32 bits, optimisant l’empreinte RAM et registres.
9. Templates variadiques typés avec les fold expressions
L’implémentation de fonctions variadiques en C (comme printf ou des helpers maison de logging/transmission bus) repose sur va_args, totalement dépourvu de sûreté de typage et source majeure de vulnérabilités de corruption de pile.
Les fold expressions de C++17 permettent aux templates variadiques de déballer leurs arguments à la compilation, avec 100 % de sûreté de typage et sans surcoût de récursion à l’exécution :
CODE_FOLD10. std::array : un remplaçant auto-conscient et à bornes vérifiées des tableaux C
Les tableaux C bruts passés à des fonctions dégénèrent automatiquement en pointeurs, perdent leur information de taille et exigent un paramètre size_t len séparé.
Le std::array de C++17 encapsule des buffers de pile à taille fixe sans aucun surcoût mémoire par rapport à un tableau C brut, tout en ajoutant des fonctions de sécurité critiques :
- Métadonnées de taille : expose
.size()à la compilation pour les assertions statiques et les boucles. - Vérification des bornes : permet aux builds de debug de détecter les dépassements de tableau (
.at()) avant de flasher le code sur le matériel. - Compatibilité algorithmes : fonctionne nativement avec les algorithmes C++ standard (comme
std::copyoustd::find).
En résumé : la leçon de l’architecte embarqué
Adopté avec discipline—RTTI (Run-Time Type Information) désactivé, exceptions C++ supprimées (-fno-exceptions) et allocation dynamique évitée—C++17 offre une sûreté à la compilation plus forte, des abstractions à coût zéro et moins de dette de maintenabilité que le C pur.
Il prouve que le logiciel embarqué moderne n’a pas à choisir entre efficacité matérielle et conception de code propre.
Envie de faire respecter ces bonnes pratiques C++17 sur l’ensemble de votre codebase embarquée ? Essayez CppDepend et validez votre architecture, vos règles de codage et votre conformité safety en CI.
