Accès rapide :
La valeur de __STDC_VERSION__
C17 ou C18 ?
Une norme de maintenance
Le cas de realloc avec une taille nulle
La fonction aligned_alloc
Modèle mémoire et objets atomiques
Mutex, conditions et threads
Signaux et fonctions atomiques
Préprocesseur et cas limites
Corrections dans l'annexe K
Pourquoi en parler ?
La norme C17, aussi appelée C18 selon les contextes, est une révision particulière du langage C. Contrairement à C99 ou C11, elle n'a pas pour objectif d'ajouter de grandes nouveautés au langage. Elle corrige essentiellement des défauts et clarifie des points du standard C11.
Lorsqu'un compilateur annonce un mode C17, la macro __STDC_VERSION__ vaut normalement 201710L.
Cette valeur permet de tester le niveau de standard demandé au compilateur.
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdio.h> int main() { #ifdef __STDC_VERSION__ printf( "__STDC_VERSION__ == %ld\n", __STDC_VERSION__ ); #else printf( "Standard C89/C90 ou mode non conforme.\n" ); #endif return 0; } |
$> gcc -std=c17 -Wall -o sample sample.c $> ./sample __STDC_VERSION__ == 201710
Vous rencontrerez les deux appellations. Le document ISO a été publié en 2018, mais la macro de version utilise la valeur
201710L. Dans GCC, les options -std=c17 et -std=c18 sont généralement équivalentes.
Pour comprendre C17, il faut retenir un point important : cette version ne cherche pas à rendre le langage plus riche. Elle intègre surtout des réponses à des défauts repérés dans C11. On parle donc davantage de corrections, de clarifications et d'harmonisation que de nouvelles fonctionnalités.
La liste complète des corrections est assez technique. Elle touche le modèle mémoire, les fonctions atomiques, les threads, quelques fonctions de la librairie standard, l'annexe K, les signaux et certains détails du préprocesseur. Nous allons plutôt regarder les points qui peuvent avoir une conséquence concrète sur votre manière d'écrire du code C.
La fonction realloc pose une question délicate lorsque la taille demandée vaut
0. Historiquement, les implémentations n'ont pas toutes adopté le même comportement : certaines libèrent le bloc,
d'autres retournent NULL, d'autres conservent l'ancien pointeur en fonction des cas.
C17 clarifie ce point, mais ne transforme pas pour autant cette écriture en bonne pratique. L'appel à realloc(ptr, 0)
est considéré comme une fonctionnalité obsolescente. Si vous voulez libérer un bloc mémoire, utilisez explicitement
free : le code sera plus clair et moins dépendant de l'implémentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdlib.h> int main() { int * values = malloc( 10 * sizeof *values ); if ( values == NULL ) { return EXIT_FAILURE; } /* Evitez realloc( values, 0 ). */ free( values ); return EXIT_SUCCESS; } |
La fonction aligned_alloc, introduite par C11, permet de demander une
allocation mémoire respectant un alignement particulier. Dans le texte initial de C11, certains cas invalides étaient mal
spécifiés. Par exemple, que devait-il se passer si l'alignement demandé n'était pas supporté, ou si la taille n'était pas un
multiple de cet alignement ?
C17 clarifie ce comportement : dans ces situations, l'appel doit échouer et renvoyer un pointeur nul. Cela ne dispense pas de respecter les contraintes de la fonction, mais cela évite de laisser ces cas dans une zone de comportement indéfini.
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdlib.h> int main() { void * buffer = aligned_alloc( 64, 1024 ); if ( buffer == NULL ) { return EXIT_FAILURE; } free( buffer ); return EXIT_SUCCESS; } |
C11 a introduit un modèle mémoire formel et une librairie d'opérations atomiques. Ce sujet est difficile, et plusieurs points du texte C11 n'étaient pas parfaitement alignés avec le modèle C++11. C17 corrige une partie de ces problèmes.
Les clarifications portent notamment sur les relations happens-before, les ordres de modification des objets atomiques, la cohérence entre lectures et écritures atomiques, ainsi que la manière dont les opérations d'allocation et de libération mémoire s'inscrivent dans ce modèle. Pour un programme séquentiel classique, cela ne change presque rien. Pour du code concurrent, c'est au contraire essentiel : le standard décrit plus précisément quelles observations sont possibles entre threads.
Les fonctions de <threads.h> ont aussi reçu des clarifications. C17 précise mieux les relations de synchronisation
autour des mutex et rapproche certains points du comportement attendu en C++11. Il faut notamment retenir qu'un verrouillage et
un déverrouillage de mutex ne sont pas de simples appels de fonctions : ils participent à l'ordre de synchronisation entre threads.
Autre point important : certaines fonctions d'attente peuvent se réveiller sans que la condition attendue soit réellement vraie.
C'est pour cette raison que l'on teste généralement une condition dans une boucle, et non avec un simple if.
1 2 3 4 |
/* Schéma classique : la condition est testée dans une boucle. */ while ( ! ready ) { cnd_wait( &condition, &mutex ); } |
Le standard C impose de fortes restrictions sur ce qu'un gestionnaire de signal peut faire. C17 clarifie certains points liés aux objets atomiques utilisables dans ce contexte. En résumé, seules certaines opérations sont autorisées, et uniquement dans des conditions très encadrées, notamment lorsque les objets atomiques concernés sont lock-free.
La règle pratique reste simple : dans un gestionnaire de signal, faites le minimum. Modifiez éventuellement un indicateur adapté, puis laissez le reste du traitement au flot normal du programme.
C17 corrige aussi des détails du préprocesseur. Le comportement de certains cas limites autour de #elif, de
#line et des macros prédéfinies a été précisé. Ce ne sont pas des changements que l'on rencontre tous les jours,
mais ils sont importants pour les outils qui génèrent du code C ou qui produisent des messages d'erreur avec des numéros de ligne
contrôlés.
L'annexe K, qui contient les fonctions dites sécurisées se terminant souvent par _s, a également été retouchée.
Certaines contraintes d'exécution et certains cas d'erreur ont été clarifiés, par exemple autour de fonctions comme
sprintf_s, gets_s, strncpy_s ou ctime_s.
Attention toutefois : cette annexe reste optionnelle et n'est pas disponible partout. Même si C17 clarifie certains points, cela ne signifie pas que ces fonctions seront présentes sur toutes les plateformes.
Même si C17 ne change pas profondément votre manière de programmer, il reste utile de connaître cette version. Elle constitue un point de stabilisation entre C11 et C23, et elle est souvent disponible sur des compilateurs modernes sans nécessiter les fonctionnalités plus récentes de C23.
En pratique, C17 est donc un bon niveau de standard pour compiler du code C moderne lorsque vous voulez bénéficier des apports de C11 tout en restant sur une version stabilisée du texte normatif.
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :