
Cet article décrit les nouvelles fonctionnalités mises en œuvre dans la partie frontale du langage C ; il ne traite pas des développements du langage C lui-même. Il ne couvre pas non plus les changements récents dans la bibliothèque C elle-même.
Le dialecte C par défaut dans GCC 13 est -std=gnu17. Vous pouvez utiliser les options de ligne de commande -std=c2x ou -std=gnu2x pour activer les fonctionnalités C2X. C2X fait référence à la prochaine version majeure du standard C, qui devrait aboutir à la version C23.
Fonctionnalités C2X
GCC 13 a mis en œuvre un grand nombre de propositions C2X. Cette section décrit les plus intéressantes d'entre elles.
nullptr
La constante nullptr est apparue pour la première fois en C++11 afin de résoudre les problèmes liés à la définition de NULL, qui peut être définie de différentes manières : (void *)0 (une constante de pointeur), 0 (un entier), etc. Cela posait des problèmes pour la résolution des surcharges, la programmation générique, etc. Bien que le C n'ait pas de surcharge de fonction, la définition protéiforme de NULL cause toujours des maux de tête. Considérez l'interaction de _Generic avec NULL : il n'est pas clair quelle fonction sera appelée parce qu'elle dépend de la définition de NULL :
Code : | Sélectionner tout |
1 2 3 4 | _Generic (NULL, void *: handle_ptr (), int: crash (), default: nop ()); |
Malheureusement, il existe des problèmes moins artificiels dans la pratique. Par exemple, des problèmes surviennent avec les opérateurs conditionnels ou lors du passage de NULL à une fonction variadique (taking ...) : dans un tel cas, l'application de va_arg à l'argument null peut faire planter le programme si une définition inattendue de NULL est rencontrée. GCC 13 introduit nullptr en C. Son type est nullptr_t et est défini dans <stddef.h>. En C2X, l'assert suivant passe donc :
Code : | Sélectionner tout |
1 2 3 4 5 | static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "nullptr_t was selected"); |
Énumérations améliorées
Les énumérations améliorées sont une autre fonctionnalité apparue pour la première fois en C++11. En C, le type sous-jacent d'une enum n'était pas spécifié dans la norme. Dans la pratique, le type est déterminé en fonction des valeurs des énumérateurs. Typiquement, le type serait unsigned int, ou, si l'une des valeurs est négative, int. Dans tous les cas, le type sélectionné doit pouvoir contenir toutes les valeurs de l'énumération. Compte tenu de cette lacune dans la spécification, les enums posent des problèmes de portabilité. Pour combler cette lacune, le langage C a adopté la syntaxe du langage C++ :
Code : | Sélectionner tout |
1 2 | enum E : long long { R, G, B } e; static_assert (_Generic (e, long long: 1, default: 0) == 1, "E type"); |
Il semble toutefois utile de mentionner que la spécification d'un mauvais type sous-jacent peut entraîner des problèmes subtils. Considérons ce qui suit :
Code : | Sélectionner tout |
enum F : int { A = 0x8000 } f;
Sur la plupart des plates-formes, ce code fonctionnera comme prévu. Cependant, la précision de int n'est pas garantie pour être au moins de 32 bits ; elle peut être de 16 bits, auquel cas l'exemple précédent ne sera pas compilé. Une meilleure variante consisterait donc à utiliser l'un des types définis dans <stdint.h>, par exemple :
Code : | Sélectionner tout |
enum F : int_least32_t { A = 0x8000 } f;
Prototypes de fonctions(...)
Le langage C, avant C2X, exigeait qu'une fonction à argument variable ait un argument nommé avant l'ellipse (...). Cette exigence était le résultat d'un bagage historique et n'est plus nécessaire, c'est pourquoi elle est supprimée. (Le C++ a toujours autorisé foo(...)).
Code : | Sélectionner tout |
1 2 | void f(int, ...); // OK void g(...); // OK in C2X |
Notez cependant que fn(...) n'est pas une fonction non prototypée, et qu'il est donc possible d'utiliser les mécanismes va_start et va_arg pour accéder à ses arguments. Une fonction non prototypée a la forme void u();.
De telles fonctions ont été supprimées dans C2X (voir ci-dessous).
Inférence de type avec auto
La déduction de type est une autre fonctionnalité apparue pour la première fois en C++11. Il s'agit d'une fonctionnalité pratique qui permet au programmeur d'utiliser l'espace réservé auto comme type dans une déclaration. Le compilateur déduira alors le type de la variable à partir de l'initialisateur :
Code : | Sélectionner tout |
auto i = 42;
Il s'agit toutefois de bien plus qu'une simple fonctionnalité permettant d'éviter de taper quelques caractères supplémentaires. Considérez :
Code : | Sélectionner tout |
auto x = foo (y);
Ici, le type de foo (y) peut dépendre de y (foo pourrait être une macro utilisant _Generic), donc changer y implique de changer le type de x. Utiliser auto dans l'exemple ci-dessus signifie que le programmeur n'a pas à changer le reste de la base de code lorsque le type de y est mis à jour. GCC propose __auto_type depuis GCC 4.9, dont la sémantique est assez proche de C2X auto, bien que pas exactement la même, et qui semble avoir été utilisé principalement dans les en-têtes standards. À la différence du C++, auto doit être utilisé en clair : il ne peut pas être combiné avec * ou [] et similaires. De plus, auto ne supporte pas les accolades autour de l'initialisateur. La fonctionnalité auto n'est activée qu'en mode C2X. Dans les modes plus anciens, auto est un spécificateur de classe de stockage redondant qui ne peut être utilisé qu'au niveau du bloc.
Le spécificateur constexpr
Le spécificateur constexpr est une autre caractéristique apparue pour la première fois en C++. En C, constexpr a été introduit avec une fonctionnalité beaucoup plus limitée. Déclarer une variable comme constexpr garantit que la variable peut être utilisée dans divers contextes d'expression constante. Le langage C exige que les objets ayant une durée de stockage statique soient initialisés avec des expressions constantes. Il s'ensuit que les variables constexpr peuvent être utilisées pour initialiser des objets à durée de stockage statique. Un autre grand avantage de constexpr est que diverses contraintes sémantiques sont vérifiées au moment de la compilation. Démontrons ces deux points à l'aide d'un exemple (notez que vous devez spécifier -std=c2x ou -std=gnu2x pour pouvoir utiliser constexpr) :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | constexpr int i = 12; static_assert (i == 12); struct X { int bf : i; }; struct S { long l; }; constexpr struct S s = { 1L }; static_assert (s.l == 1L); constexpr unsigned char q = 0xff + i; // initializer not representable in type of object |
Spécificateurs de classe de stockage dans les littéraux composés
Un composé littéral est un moyen de créer des objets sans nom qui ont généralement une durée de stockage automatique. Comme il s'agit de lvalues, il est permis de prendre leur adresse :
Code : | Sélectionner tout |
1 2 | int *p = (int []){2, 4}; // p points to the first element of an array of two ints const int *q = &(const int){1}; |
L'utilisation de certains spécificateurs de classe de stockage (comme constexpr, static, thread_local) dans les littéraux composés en mode C2X est autorisée. Cela permet de modifier la durée de vie du littéral composé ou d'en faire une constante de littéral composé avec le mot-clé constexpr :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | struct S { int i; }; void f (void) { static struct S s = (constexpr struct S){ 42 }; } int * g (void) { return &(static int){ 42 }; } |
Notez que même si typedef, extern et auto sont des spécificateurs de classe de stockage, ils ne sont pas autorisés dans les littéraux composés.
C2X typeof
C2X a standardisé typeof, une fonctionnalité qui a été supportée en tant qu'extension GNU pendant de nombreuses années et qui permet au programmeur d'obtenir le type d'une expression. En plus de typeof, C2X ajoute également typeof_unqual, qui supprime tous les qualificateurs et _Atomic du type résultant :
Code : | Sélectionner tout |
1 2 3 4 | int i; volatile int vi; extern typeof (vi) vi; // OK, no conflict extern typeof_unqual (vi) i; // OK, no conflict |
Une différence mineure entre la version GNU et la version standard est le traitement de la propriété noreturn d'une fonction : la variante GNU de typeof prend noreturn comme partie du type d'un pointeur de fonction, mais la version standard ne le fait pas.
Notez que C++11 a standardisé une fonctionnalité similaire sous le nom de decltype, ce qui fait que l'on se retrouve avec deux noms pour une fonctionnalité presque identique.
Nouveaux mots-clés
Cette proposition harmonise davantage le C et le C++ en faisant de alignas, alignof, bool, false, static_assert, thread_local et true des mots-clés ordinaires en mode C2X. Par conséquent, cette unité de traduction compilera correctement en mode C2X :
Code : | Sélectionner tout |
static_assert (true, "");
Cette modification peut casser le code existant, par exemple
Code : | Sélectionner tout |
int alignof = 42;
ne se compilera pas en mode C2X.
L'attribut noreturn
Une autre amélioration de la compatibilité pour rapprocher le C et le C++. Le C11 a ajouté le spécificateur de fonction _Noreturn pour signaler au compilateur qu'une fonction ne retourne jamais à son appelant, mais _Noreturn ne fonctionne qu'en C. C2X a donc ajouté un attribut [[noreturn]] standard tout en marquant simultanément _Noreturn comme obsolète.
Code : | Sélectionner tout |
[[noreturn]] void exit (int);
Accolades...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.