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
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.
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 |
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" ); } |
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; } |
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
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é.
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 |
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 ... |
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 ^~~~~~~~~~~~~ $>
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; } |
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; } |
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; } |
Il ne reste plus qu'à compiler et à exécuter le programme.
$> gcc -O3 -o sample main.c $> ./sample miniInt(3, 6) + 2 == 5 $>
inline
.
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 ); |
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; } |
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
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
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 !
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; |
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.
register
.
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; |
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 :