IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

GCC 13 se dote de nouvelles fonctionnalités dans la partie frontale du langage C pour le rendre plus proche du C++
Et faire en sorte que la programmation en C soit plus facile et plus sûre

Le , par Anthony

27PARTAGES

24  0 
La dernière version majeure de la GNU Compiler Collection (GCC), la 13.1, a été publiée en avril 2023. Comme toutes les versions majeures de GCC, cette version apportera de nombreux ajouts, améliorations, corrections de bogues et nouvelles fonctionnalités. GCC 13 est déjà le compilateur système de Fedora 38. Les utilisateurs de Red Hat Enterprise Linux (RHEL) obtiendront GCC 13 dans le Red Hat GCC Toolset (RHEL 8 et RHEL 9). Il est également possible d'essayer GCC 13 sur godbolt.org et d'autres sites similaires.

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

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de VBurel
Membre averti https://www.developpez.com
Le 09/05/2023 à 9:12
L'intérêt premier du 'C' est son caractère universel (compilable sur toute plateforme).
Donc ceux qui programment en 'C' aujourd'hui utilisent le coeur du langage (souvent 1990 compatible) pour pouvoir conserver un code opérationnel (parfois sur plusieurs décennies).
et n'ont que faire des améliorations syntaxiques créant des points d'incompatibilité...

En conclusion, je ne vois pas l'intérêt de faire évoluer le 'C' vers le 'C++'. Laissons chacun utiliser le langage qu'il préfère.
2  0 
Avatar de foetus
Expert éminent sénior https://www.developpez.com
Le 12/05/2023 à 8:15
Citation Envoyé par selmanjo Voir le message
Enfin, un langage C qui grandit !
Justement, c'est au jugé de chacun
Moi, je pense que ce n'est pas avec des fonctionnalité du C++ moderne (nullprt, constexpr, énumération typée, …) que le C grandit.

Si tu veux que le C grandit, il faut d'autres ajouts (*) : ajout de l'Unicode (avec 1 vrai type caractère/ chaîne de caractères transparent), ajout des containers (table de hachage, tableau associatif, …), ajout des "threads", support du json, …
Là, j'ai l'impression que c'est + pour aider l'intégration du C++ moderne dans des projets C (ou l'inverse, l'intégration du C dans des projets C++ moderne)
Si les langages comme Python ou JavaScript ont "le vent en poupe", c'est pour leur simplicité d'utilisation et leur "très large possibilité" … même si je sais que le C restera du C (les tests, les casts, les conversions, …)

Édit : On peut aussi rajouter Linux qui commence à utiliser/ tester Rust. C'est bien que le C pose des questions sur "son utilisation".

Mais pour ces ajouts, (*) on le sait depuis 2011 : que ce soit le C++ ou le C, ces langages n'auront jamais 1 bibliothèque standard étendue/ complète.
L'intégration des threads dans le C++11, fut trop léger (trop bas niveau). Pareil pour l'Unicode : c'est juste en gros MBCS.

Après, j'ai lu que le GCC a été critiqué pour son immobilisme face à LLVM : donc peut-être des mises à jour pour dire "GCC fait quelque chose"
2  0 
Avatar de chrtophe
Responsable Systèmes https://www.developpez.com
Le 10/05/2023 à 7:18
Ce n'est pas forcément le cas de tout le monde. De nouvelles fonctionnalités, tu les utilise ou pas.
1  0 
Avatar de selmanjo
Membre régulier https://www.developpez.com
Le 12/05/2023 à 0:54
On aime les nouvelles fonctionnalités.
Enfin, un langage C qui grandit !
Quand on est débutant en langage C, c'est un véritable régal.&#128523;

*(void *) 0xcc00leee;
0  0