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é 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;


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é 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