Participer au site avec un Tip
Rechercher
 

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 :

Vous êtes un professionnel et vous avez besoin d'une formation ? Programmation avec
Le langage C
Voir le programme détaillé

Les nouveautés introduites par C23

Les nouveautés introduites par C17 Les principales options de gcc


Accès rapide :
La valeur de __STDC_VERSION__
bool, true et false
Des constantes typées avec constexpr
Le mot clé nullptr
auto, typeof et typeof_unqual
Les entiers à largeur précise
Littéraux binaires et séparateurs de chiffres
Enumérations à type fixé
char8_t et les chaînes UTF-8
static_assert
Attributs standards
Un préprocesseur plus pratique
Nouveaux en-têtes et fonctions utiles
Quelques nettoyages du langage

C23 constitue une évolution nettement plus visible que C17. Le langage reste du C, mais plusieurs éléments longtemps présents sous forme d'extensions de compilateurs, ou déjà familiers aux développeurs C++, entrent maintenant dans le standard. Comme toujours, il faut distinguer le support du compilateur et celui de la librairie C : vous pouvez parfois utiliser une syntaxe C23 alors que certains nouveaux en-têtes ne sont pas encore disponibles sur votre plateforme.

La valeur de __STDC_VERSION__

En mode C23, la macro __STDC_VERSION__ vaut 202311L. Avec un compilateur récent, vous pouvez demander ce niveau de standard avec l'option -std=c23. Sur des versions plus anciennes de GCC ou Clang, vous rencontrerez parfois encore l'option transitoire -std=c2x.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
#include <stdio.h>

int main() {

    printf( "__STDC_VERSION__ == %ld\n", __STDC_VERSION__ );
    return 0;
}
Tester le niveau du standard C

bool, true et false

Depuis C99, on utilisait l'entête <stdbool.h> pour obtenir les noms bool, true et false. Avec C23, ces noms font directement partie du langage. Le code gagne donc en lisibilité, surtout pour les débutants qui arrivent d'autres langages.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
#include <stdio.h>

int main() {

    bool connected = true;

    if ( connected ) {
        printf( "Connexion active.\n" );
    }

    return 0;
}
Utilisation directe du type bool

Des constantes typées avec constexpr

Le mot clé constexpr permet de définir de vraies constantes nommées, typées et utilisables dans des expressions constantes. Il ne faut pas le confondre avec le constexpr du C++ : en C23, il concerne les objets, pas les fonctions. C'est néanmoins très pratique pour remplacer certains #define ou certaines énumérations utilisées uniquement pour produire une constante entière.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
#include <stdio.h>

constexpr int BufferSize = 256;
static_assert( BufferSize == 256 );

int main() {

    char buffer[BufferSize];

    printf( "Taille du buffer : %zu octets\n", sizeof buffer );
    return 0;
}
Constantes nommées avec constexpr

Le mot clé nullptr

C23 introduit nullptr, une constante de pointeur nul plus explicite que NULL. L'objectif est d'éviter certaines ambiguïtés liées au fait que NULL est historiquement défini par une macro. Le type associé, nullptr_t, est notamment utile avec _Generic ou pour écrire des interfaces plus explicites.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
#include <stdio.h>

int main() {

    int * pointer = nullptr;

    if ( pointer == nullptr ) {
        printf( "Le pointeur est nul.\n" );
    }

    return 0;
}
Utilisation de nullptr

auto, typeof et typeof_unqual

C23 récupère aussi une idée longtemps disponible dans certains compilateurs : laisser le compilateur déduire un type local ou récupérer le type d'une expression. Le mot clé auto sert maintenant à l'inférence de type pour des définitions d'objets. Les opérateurs typeof et typeof_unqual permettent, eux, de réutiliser le type d'une expression. Le second retire les qualificateurs comme const ou volatile.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
#include <stdio.h>

int main() {

    auto counter = 42u;               /* unsigned int */
    typeof( counter ) next = counter + 1u;

    const int limit = 10;
    typeof_unqual( limit ) value = limit;
    value++;

    printf( "next == %u\n", next );
    printf( "value == %d\n", value );

    return 0;
}
Inférence de type et réutilisation d'un type
l'inférence de type de C23 reste volontairement limitée : on ne déduit pas le type de retour d'une fonction comme en C++.

Les entiers à largeur précise

Le nouveau type _BitInt(N) permet de demander un entier représenté sur un nombre précis de bits. Cette fonctionnalité vise surtout les traitements bas niveau, les formats binaires, les protocoles ou certains calculs embarqués. Pour du code applicatif classique, les types de <stdint.h> restent souvent plus simples à lire.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
#include <stdio.h>

int main() {

    unsigned _BitInt(5) flags = 0b1'0110;
    _BitInt(12) temperature = -125;

    printf( "flags == %u\n", (unsigned) flags );
    printf( "temperature == %d\n", (int) temperature );

    return 0;
}
Entiers à largeur précise

Littéraux binaires et séparateurs de chiffres

C23 ajoute aussi des écritures plus confortables pour certaines constantes numériques. Les littéraux binaires utilisent le préfixe 0b ou 0B, et le caractère ' peut être utilisé comme séparateur visuel dans les nombres. Le standard ajoute aussi des suffixes liés aux entiers à largeur précise, comme wb et uwb.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
#include <stdio.h>

int main() {

    int mask = 0b1111'0000;
    int value = 1'000'000;

    printf( "mask == %d\n", mask );
    printf( "value == %d\n", value );

    return 0;
}
Constantes numériques en C23

La famille printf gagne aussi un format binaire avec %b et %B. C'est agréable pour afficher directement des masques de bits, mais il faudra vérifier le support réel de votre librairie C.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
#include <stdio.h>

int main() {

    unsigned int flags = 0b1010'1100u;

    printf( "flags == %b\n", flags );
    printf( "flags == 0B%B\n", flags );

    return 0;
}
Affichage binaire avec printf

Enumérations à type fixé

Les énumérations peuvent maintenant préciser leur type sous-jacent. C'est utile quand la taille de stockage fait partie du contrat : format de fichier, protocole réseau, structure partagée avec du matériel, etc. Sans cette précision, le compilateur conserve une certaine liberté de représentation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
#include <stdio.h>


enum LogLevel : unsigned char {
    LOG_INFO = 1,
    LOG_WARNING = 2,
    LOG_ERROR = 3
};

int main() {

    enum LogLevel level = LOG_WARNING;

    printf( "Taille : %zu octet(s)\n", sizeof level );
    printf( "Valeur : %u\n", (unsigned) level );

    return 0;
}
Enumération avec type sous-jacent explicite

char8_t et les chaînes UTF-8

C23 introduit le type char8_t, défini dans <uchar.h>, pour représenter explicitement des unités de code UTF-8. Les chaînes préfixées par u8 ont donc un type plus précis qu'avant. C'est utile pour clarifier les interfaces qui travaillent vraiment en UTF-8.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
#include <stdio.h>
#include <uchar.h>

int main() {

    char8_t message[] = u8"KooR";

    printf( "Taille : %zu octets\n", sizeof message );
    return 0;
}
Une chaîne UTF-8 typée avec char8_t

static_assert

Les assertions statiques existaient déjà avec C11 sous la forme _Static_assert. C23 standardise l'écriture static_assert, plus naturelle et plus proche de ce que l'on trouve en C++. Le message devient optionnel, ce qui permet d'écrire des vérifications très courtes.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
#include <limits.h>

static_assert( CHAR_BIT == 8, "Cette plateforme n'utilise pas des octets de 8 bits" );
static_assert( sizeof( int ) >= 4 );

int main() {
    return 0;
}
Assertion statique

Attributs standards

C23 renforce la syntaxe des attributs avec la forme [[...]]. Certains attributs servent surtout à documenter une intention auprès du compilateur : ne pas ignorer une valeur de retour, signaler une fonction obsolète, indiquer une variable volontairement inutilisée, ou rendre explicite une chute entre deux branches d'un switch.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
#include <stdio.h>

[[nodiscard]] int load_configuration() {
    return 0;
}

int main() {

    [[maybe_unused]] int debugCounter = 0;

    load_configuration();     /* Le compilateur peut signaler cet oubli. */
    return 0;
}
Quelques attributs standards

Un préprocesseur plus pratique

Le préprocesseur reçoit plusieurs ajouts utiles. Les directives #elifdef et #elifndef évitent des expressions conditionnelles inutilement longues. La macro __VA_OPT__ simplifie les macros variadiques dont la partie variable peut être vide. On trouve aussi des tests comme __has_include ou __has_c_attribute, ainsi que #embed pour incorporer des données externes dans une unité de traduction.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
#include <stdio.h>

#if __has_include(<threads.h>)
    #include <threads.h>
#elifdef USE_PTHREADS
    #include <pthread.h>
#else
    #warning "Aucune librairie de threads détectée"
#endif

#define LOG(format, ...) fprintf( stderr, format __VA_OPT__(,) __VA_ARGS__ )

int main() {

    LOG( "Démarrage\n" );
    LOG( "Valeur : %d\n", 42 );

    return 0;
}
Préprocesseur C23
 1 
 2 
 3 
static const unsigned char icon[] = {
#embed "icon.bin"
};
Incorporation d'un fichier binaire
#embed est un ajout très pratique, mais son support est encore récent. Vérifiez votre chaîne de compilation avant de l'utiliser dans un projet portable.

Nouveaux en-têtes et fonctions utiles

C23 n'ajoute pas uniquement de la syntaxe. La librairie standard évolue elle aussi, avec notamment <stdckdint.h> pour les opérations arithmétiques vérifiées, <stdbit.h> pour certaines opérations sur les bits, memset_explicit pour effacer explicitement des données sensibles, ainsi que des fonctions déjà connues dans certains environnements POSIX comme strdup, strndup ou memccpy.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
#include <stdio.h>
#include <stdckdint.h>

int main() {

    int result;

    if ( ckd_add( &result, 2'000'000'000, 2'000'000'000 ) ) {
        printf( "Débordement détecté.\n" );
        return 1;
    }

    printf( "result == %d\n", result );
    return 0;
}
Addition entière vérifiée
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
#include <stdio.h>
#include <stdbit.h>

int main() {

    unsigned int flags = 0b1011u;

    printf( "Nombre de bits à 1 : %u\n", stdc_count_ones( flags ) );
    return 0;
}
Compter les bits positionnés

Quelques nettoyages du langage

C23 retire ou clarifie aussi plusieurs formes historiques du langage. Les définitions de fonctions à l'ancienne, héritées du C K&R, disparaissent du standard. Les déclarations implicites de fonctions et le vieux int implicite ne doivent plus être considérés comme du C moderne. Autre point important : une liste de paramètres vide indique maintenant réellement qu'une fonction ne prend pas de paramètre, ce qui rapproche l'écriture int main() de int main( void ).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
/* A éviter dans du code C moderne. */
int legacy();

/* Plus explicite. */
int compute(void);
int compute(void) {
    return 42;
}
Ecriture moderne des prototypes

Au final, C23 est une mise à jour importante, mais il ne faut pas se précipiter sans vérifier l'outillage. Beaucoup d'ajouts sont déjà très utiles pour écrire du C plus clair, mais leur disponibilité dépendra encore pendant quelque temps du compilateur, de la librairie C et des options de compilation utilisées.

Les nouveautés introduites par C17 Les principales options de gcc




Vous êtes un professionnel et vous avez besoin d'une formation ? Programmation avec
Le langage C
Voir le programme détaillé