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 initialisatrices vides
C2X a normalisé les accolades initialisatrices vides ({}) et GCC 13 met en œuvre cette proposition. Certains cas étaient déjà supportés en tant qu'extension GNU (par exemple, l'initialisation d'un tableau ou d'une structure), mais il est désormais possible d'utiliser {} pour initialiser une variable scalaire ou un tableau de longueur variable :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | int i = {}; int arr[10] = {}; struct S { int i; }; struct S s = {}; void g (void) { int n = 10; int vla[n] = {}; } |
Macro unreachable
C2X apporte la macro unreachable(), définie dans <stddef.h>, qui est un raccourci pratique pour la fonction intégrée de GCC __builtin_unreachable() :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | #include <stddef.h> int foo (int x) { if (x < 0) unreachable (); return x & 1; } |
Suppression des fonctions non prototypées
Les fonctions non prototypées en C étaient de la forme int foo(), qui est une fonction foo qui renvoie un entier qui prend un nombre non spécifié d'arguments de types non spécifiés. C'est très dangereux car le compilateur ne peut effectuer aucune vérification lorsqu'une telle fonction est utilisée.
Dans C2X, int foo() est équivalent à int foo(void), qui est une fonction foo qui retourne un entier et ne prend aucun argument.
Nouveaux avertissements
L'interface C a gagné quelques nouveaux avertissements dans GCC 13. Par exemple, -Wxor-used-as-pow, qui a été décrit dans la partie C++ du billet de blog de GCC 13. Il y a un nouvel avertissement spécifique pour le front-end C.
-Wenum-int-mismatch
En C, un type énuméré est compatible avec char, un type entier signé ou un type entier non signé, donc le code suivant se compile si le type sous-jacent de enum E est int :
Code : | Sélectionner tout |
1 2 3 | enum E { l = -1, z = 0, g = 1 }; int foo(void); enum E foo(void) { return z; } |
Toutefois, comme indiqué précédemment, le choix du type sous-jacent de l'énumération est défini par l'implémentation. Comme le code ci-dessus est probablement une erreur et constitue un problème de portabilité (le code ne se compilera pas si un type différent de int est choisi comme type sous-jacent), GCC 13 implémente un nouvel avertissement qui signale les incohérences entre les types enum et entier. Pour le code ci-dessus, l'avertissement ressemble à ce qui suit :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | q.c:5:10: warning: conflicting types for ‘foo’ due to enum/integer mismatch; have ‘enum E(void)’ [-Wenum-int-mismatch] 5 | enum E foo(void) { return z; } | ^~~ q.c:4:7: note: previous declaration of ‘foo’ with type ‘int(void)’ 4 | int foo(void); | |
Conclusion
GCC 13 met en œuvre de nombreuses propositions de C2X. Ces propositions rapprochent un peu plus les langages C et C++ en combinant certaines caractéristiques, et rendent la programmation en C plus facile et plus sûre.
Source : Annonce de l'ajout des fonctionnalités C à GCC 13
Et vous ?
Que pensez-vous des nouvelles fonctionnalités C apportées à GCC 13 ? Trouvez-vous qu'elles sont utiles et cohérentes ?
Quel est votre avis général sur la collection de compilateurs GNU ?
Voir aussi
La version 13.1 de GCC prend en charge le langage Modula-2 et offre davantage de fonctionnalités pour C23/C++23
La première version officielle de GCC 13 est sur le point d'être publiée, mais n'inclura pas gccrs, le frontend pour le langage Rust, le compilateur gccrs ne serait pas prêt pour du "vrai" code Rust
GCC se dote d'un nouveau frontend pour le langage de programmation Rust, une version préliminaire de ce compilateur appelé "gccrs" devrait être incluse dans GCC 13