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 :

Quelques mots clés complémentaires

Les pointeurs sur fonctions Les nouveautés introduites par C99


Accès rapide :
Le mot clé extern
Le mot clé static
Variable globale statique
Variable locale statique
Le mot clé inline (C99)
Le mot clé restrict (C99)
Le mot clé register
Le mot clé volatile

Le mot clé extern

Le mot clé extern s'utilise traditionnellement lors d'une déclaration de donnée dans un fichier d'entête (.h). Il permet d'indiquer au compilateur que la donnée existe bien et quel est son type, mais précise surtout au compilateur de ne pas réserver un emplacement mémoire maintenant et résoudre cet emplacement mémoire plus tard : durant de la phase d'édition des liens.

un fichier de déclaration (un fichier d'extension .h) ne doit en aucun cas réserver d'espace mémoire, sans quoi, si ce fichier est inclut dans plusieurs fichiers .c, la donnée sera instanciée plusieurs fois et une erreur sera produire à la phase d'édition des liens.

Nous allons envisager un petit exemple basé sur trois fichiers : un fichier d'entête (un .h) et deux fichiers d'implémentation (deux .c). Le fichier d'entête déclare une variable externe et une fonction : il sera inclus dans les deux fichiers d'implémentation. Voici le code de ce fichier de déclaration.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
#ifndef __COMMON
#define __COMMON

extern int globalCounter;       // La variable est déclarée mais non réservée en mémoire

void doSomething();             // Déclaration de la fonction doSomething

#endif
Fichier common.h

Voici le code du fichier .c qui fournit la réservation mémoire de la variable ainsi que l'implémentation de la fonction.

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

#include "Common.h"     // Entre double guillemets, car ce fichier est défini dans le projet.

// On réserve la mémoire pour la variable globale et on l'initialise.
int globalCounter = 0;

// On implémente la fonction doSomething
void doSomething() {
    globalCounter ++;
    printf( "doSomething invoked\n" );
}
Fichier impl.c

Et pour finir on code le second fichier qui fournit une fonction main.

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

#include "Common.h"     // Entre double guillemets, car ce fichier est défini dans le projet.

int main() {

    // On connait la variable grâce à la déclaration externe définie dans le .h
    // On peut donc y avoir accès.
    printf( "Counter == %d\n", globalCounter );         // Affiche 0
    doSomething();
    printf( "Counter == %d\n", globalCounter );         // Affiche 1    

    return EXIT_SUCCESS;

}
Fichier main.c

Il est temps de compiler le programme constitué de ces trois fichiers et de vérifier que tout fonctionne parfaitement.

$> gcc -c impl.c 
$> gcc -c main.c 
$> gcc -o sample impl.o main.o
$> ./sample 
Counter == 0
doSomething invoked
Counter == 1
$> 

Le mot clé static

Variable globale statique

Le mot clé static permet de réduire la visibilité de l'élément au bloc dans lequel il est défini. Dans le cas d'une variable globale qualifiée de statique, la notion de bloc correspond au fichier d'implémentation dans lequel elle est définie. On ne peut donc pas l'utiliser de l'extérieur du fichier considéré.

l'utilisation conjointe des deux mots clés extern et static est donc impossible. Voici un exemple qui montre le problème.
 1 
 2 
 3 
 4 
 5 
 6 
#ifndef __COMMON
#define __COMMON

extern int globalCounter;       // La variable est déclarée mais non réservée en mémoire


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

#include "Common.h"     // Entre double guillemets, car ce fichier est défini dans le projet.

// On réserve la mémoire pour la variable globale et on l'initialise.
static int globalCounter = 0;

// ... suite ...
Fichier impl.c

Et voici l'erreur de compilation produite.

$> gcc -c impl.c
impl.c:6:12: error: la déclaration statique de « globalCounter » suit une déclaration non statique
 static int globalCounter = 0;
            ^~~~~~~~~~~~~
In file included from impl.c:3:
Common.h:4:12: note: la déclaration précédente de « globalCounter » était ici
 extern int globalCounter;       // La variable est déclarée mais non réservée en mémoire
            ^~~~~~~~~~~~~
$>

Variable locale statique

Une variable locale statique correspond, plus ou moins, à une variable globale accessible uniquement dans le contexte d'une fonction donnée. Mais sa durée de vie correspond bien à une variable globale : en conséquence son état est maintenu entre chaque appel à la fonction ayant définie la variable locale statique. Voici un petit exemple d'utilisation.

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

void test() {
    // Notre variable locale statique
    // La valeur initiale n'est affectée que la première fois.
    // Ensuite, l'état de la variable est conservée entre les appels
    static int counter = 0;

    counter++;
    printf( "Counter == %d\n", counter );
}


int main() {

    test();     // Afficher: Counter == 0
    test();     // Afficher: Counter == 1
    test();     // Afficher: Counter == 2

    return EXIT_SUCCESS;

}
Fichier sample.c

Le mot clé inline (C99)

Le mot clé inline est une demande d'optimisation faite au compilateur : on demande à ce que le corps de la fonction inline soit inséré en lieu et place de son appel. Ainsi il n'y aura pas d'appel de fonction et le code sera plus efficace. Traditionnellement, on utilise cette possibilité « d'inlining » sur des fonctions de petite taille et qui sont invoquées fréquemment.

Dans les faits, le mot clé réservé inline provient du langage C++ mais a été remonté dans le langage C à partir de sa norme C ISO 1999. Il est aussi important de noter que les deux langages (C et C++) gèrent ce mot clé de manière légèrement différente. Dans le cas du langage C et de l'utilisation de gcc, il faut activer les optimisations du compilateur (option -O) pour que le mot clé prenne effet (et que la compilation se passe sans encombre).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
#ifndef __COMMON
#define __COMMON

#define MINI(a, b)  ((a)<(b) ? (a) : (b))

// On demande au compilateur (tant que possible) de ne pas avoir de fonction
// mais plutôt d'injecter le corps de la fonction inline en lieu et place
// de chaque appel.
inline int miniInt( int a, int b ) {
    return a<b ? a : b;
}


Fichier Common.h : exemple de définition d'une fonction inline
comme la fonction n'existera pas (elle est inlinée), on peut la définir dans le fichier .h et il n'y aura pas de problème de multiples définitions de la fonction.

Maintenant on utilise la fonction dans un fichier d'implémentation.

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

#include "Common.h"

int main() {

    int a = 3;
    int b = 6;
    int result = miniInt( a, b ) + 2;

    printf( "miniInt(3, 6) + 2 == %d\n", result );

    return EXIT_SUCCESS;

}
Fichier main.c : exemple d'utilisation de la fonction inline

Il ne reste plus qu'à compiler et à exécuter le programme.

$> gcc -O3 -o sample main.c
$> ./sample
miniInt(3, 6) + 2 == 5
$>
de plus en plus, on part du principe que le développeur n'est pas le plus indiqué pour trouver les meilleurs points d'optimisations d'un programme. Avec le temps les optimiseurs intégrés dans votre compilateur sont devenus très performant. Désormais, on préfère donc laisser faire les optimiseurs pour faire ce travail d'optimisation au mieux et on évite d'utiliser ce mot clé inline.

Le mot clé restrict (C99)

A l'instar du mot clé inline, le mot clé restrict permet d'obtenir des optimisations de la part du compilateur. Ce mot clé s'utilise sur une déclaration de paramètre de type pointeur, pour indiquer qu'il est le seul moyen de manipuler la zone pointée dans la fonction considérée. Si ce n'est pas le cas et que le mot clé restrict est utilisé alors le comportement est non spécifié.

Du point de vue de l'utilisateur de la fonction, ce mot clé n'a aucune signification particulière (il ne s'adresse qu'au compilateur).

A titre d'exemple, voici comment est déclarée (depuis C99) la fonction memcpy.

 1 
 2 
#include <string.h>
void * memcpy( void * restrict destination, const void * restrict source, size_t size );
Déclaration de la fonction memcpy

Afin de confirmer l'optimisation faite par le compilateur, regardons le code suivant :

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

void multiIncrements( int * ptrA, int * ptrB, int * restrict value ) {
  *ptrA += *value;
  *ptrB += *value;
}

int main() {

    int x = 33;
    int y = 44;
    int inc = 10;
    multiIncrements( &x, &x, &inc );

    printf("x==%d, y==%d\n", x, y );

    return EXIT_SUCCESS;
}
Fichier sample.c : un exemple d'utilisation du mot clé restrict

Je vous propose de compiler votre programme avec la ligne de commande suivante : elle permet d'arrêter la compilation réalisée par gcc à l'étape de production du code assembleur.

$> gcc -S -O3 sample.c

Lancez une compilation sans le mot clé restrict. Voici un extrait du fichier produit (sur un processeur type i7).

multiIncrements:
.LFB22:
    .cfi_startproc
    movl    (%rdx), %eax
    addl    %eax, (%rdi)
    movl    (%rdx), %eax
    addl    %eax, (%rsi)
    ret
l'instruction movl (%rdx), %eax qui charge la valeur pointée par la variable value est présente deux fois dans le code.

Lancez maintenant une seconde compilation avec le mot clé restrict. Voici un extrait du fichier produit (encore une fois, sur un processeur type i7).

multiIncrements:
.LFB22:
    .cfi_startproc
    movl    (%rdx), %eax
    addl    %eax, (%rdi)
    addl    %eax, (%rsi)
    ret
l'instruction movl (%rdx), %eax qui charge la valeur pointée par la variable value n'apparaît plus qu'une seule et unique fois. Le code sera donc plus performant !

Le mot clé register

Ce mot clé s'utilise en tête d'une déclaration de variable, comme le montre l'exemple ci-dessous.

 1 
register int value = 0;
Exemple d'utilisation du mot clé register

Il permet d'indiquer au compilateur de réserver un registre du processeur pour la gestion de cette variable. C'est un mécanisme pouvant permettre d'optimiser les codes produits par le compilateur à la condition absolue de maîtriser ce sujet.

de plus en plus, on part du principe que le développeur n'est pas le plus indiqué pour trouver les meilleurs points d'optimisations d'un programme. Avec le temps les optimiseurs intégrés dans votre compilateur sont devenus très performant. Désormais, on préfère donc laisser faire les optimiseurs pour faire ce travail d'optimisation au mieux et on évite d'utiliser ce mot clé register.

Le mot clé volatile

Ce mot clé permet de prévenir le compilateur qu'une variable peut être utilisée de manière extérieure au programme. A partir de là, le compilateur sait qu'il ne pourra plus jouer certaines optimisations (celle induite par le mot clé register, par exemple) afin de garantir la cohérence du programme. Ce mot clé prend tout son sens quand, par exemple, on code des drivers (des pilotes de périphériques) pour un système d'exploitation. Voici un exemple d'utilisation.

 1 
volatile int value = 0;
Exemple d'utilisation du mot clé volatile


Les pointeurs sur fonctions Les nouveautés introduites par C99