Programmation C/Classe de stockage
Il est possible de construire des types dérivés des types de base du langage en utilisant plusieurs combinaisons, deux étant illustrées dans ce chapitre : les classes de stockage et les qualificateurs.
Classe de stockage
modifierLe langage C permet de spécifier, avant le type d'une variable, un certain nombre de classes de stockage :
auto
: pour les variables locales ;extern
: déclare une variable sans la définir ;register
: demande au compilateur de faire tout son possible pour utiliser un registre processeur pour cette variable ;static
: rend une définition de variable persistante.
Les classes static
et extern
sont, de loin, les plus utilisées. register
est d'une utilité limitée, et auto
est maintenant obsolète.
Une variable, ou un paramètre de fonction, ne peut avoir qu'au plus une classe de stockage.
Classe 'static'
modifierL'effet de la classe 'static' dépend de l'endroit où l'objet est déclaré :
- Objet local à un bloc : la valeur de la variable sera persistante entre les différents appels de la fonction où se trouve le bloc. La variable ne sera visible que dans la fonction, mais ne sera pas réinitialisée à chaque appel de la fonction. L'intérêt est de garantir une certaine encapsulation, afin d'éviter des usages multiples d'une variable globale. Qui plus est, cela permet d'avoir plusieurs fois le même nom, dans des fonctions différentes.
Exemple :
#include <stdio.h>
void f(void)
{
static int i = 0; /* i sera initialisée à 0 à la compilation seulement */
int j = 0; /* j sera initialisée à chaque appel de f */;
i++;
j++;
printf("i vaut %d et j vaut %d.\n", i, j);
}
int main(void)
{
f();
f();
f();
return 0;
}
Résultat d'exécution du code ci dessus :
i vaut 1 et j vaut 1.
i vaut 2 et j vaut 1.
i vaut 3 et j vaut 1.
Il est déconseillé de redéfinir plusieurs fois la même variable statique dans différents blocs, car le résultat peut être surprenant :
#include <stdio.h>
void toto(void) {
static char toto = 'A';
printf("deb fun: %c\n", toto);
{
printf("deb block: %c\n", toto);
static char toto = 'M';
printf("fin block: %c\n", toto);
toto ++;
}
toto++;
printf("fin fun: %c\n", toto);
}
int main(void)
{
toto();
toto();
return 0;
}
Affiche
deb fun: A deb bloc: A fin bloc: M fin fun: B deb fun: B deb bloc: B fin bloc: N fin fun: C
On voit que la durée de vie de la variable de bloc statique ne commence qu'à partir de sa déclaration, même si la déclaration n'est exécutée qu'une fois au cours du programme.
- Objet global et fonction : comme une variable globale est déjà persistante, le mot-clé
static
aura pour effet de limiter la portée de la variable ou de la fonction au seul fichier où elle est déclarée, toujours dans le but de garantir un certain niveau d'encapsulation.
Une variable de classe statique est initialisée au moment de la compilation à zéro par défaut (contrairement aux variables dynamiques qui ont une valeur initiale indéterminée). Elle peut être initialisée explicitement à n'importe quelle valeur constante.
Classe 'extern'
modifierLe mot clé extern
permet de déclarer une variable sans la définir. C'est utile pour la compilation séparée, pour définir une variable ou une fonction dans un fichier, en permettant à des fonctions contenues dans d'autres fichiers d'y accéder.
Toutes les variables globales et fonctions qui ne sont pas déclarées (ou définies) static
sont externes par défaut.
Les mots clés static
et extern
sont employés pour distinguer, dans un fichier C, les objets et fonctions « privés », qui ne doivent être utilisés que depuis l'intérieur du fichier, et ceux qui sont « publics », et pourront être accessibles depuis d'autres fichiers.
Classe 'register'
modifierL'usage de ce mot clé est utile dans un contexte de logiciel embarqué. Indique que la variable devrait être stockée dans un registre du processeur. Cela permet de gagner en performance par rapport à des variables qui seraient stockées dans un espace mémoire beaucoup moins rapide, comme une pile placée en mémoire vive.
Ce mot-clé a deux limitations principales :
- Les registres du processeur sont limités. Leur nombre peut varier en fonction du processeur. Sur un PC (d'architecture AMD64?), ils sont au nombre de 13, dont seulement 4 servent au stockage (EAX,EBX,ECX,EDX). Il est donc inutile de déclarer une structure entière ou un tableau avec le mot clé
register
. - Qui plus est, les variables placées dans des registres sont forcément locales à des fonctions ; on ne peut pas définir une variable globale en tant que registre.
Aujourd'hui, ce mot-clé est déconseillé sauf pour des cas particuliers, les compilateurs modernes sachant généralement mieux que le programmeur comment optimiser et quelles variables placer dans les registres.
#include <stdio.h>
int main(void)
{
register short i, j;
for (i = 1; i < 1000; ++i)
{
for(j = 1; j < 1000; ++j)
{
printf("\n %d %d", i, j);
}
}
return 0;
}
Classe 'auto'
modifierCette classe est un héritage du langage B. En C, ce mot-clé sert pour les variables locales à une fonction non-statiques, dites aussi automatiques. Mais une variable déclarée localement à une fonction sans qualificateur static
étant implicitement automatique, ce mot-clé est inutile en C.
Qualificateurs
modifierLe C définit trois qualificateurs pouvant influer sur une variable :
const
: pour définir une variable dont la valeur ne devrait jamais changer ;restrict
: permet une optimisation pour la gestion des pointeurs ;volatile
: désigne une variable pouvant être modifiée notamment par une source externe indépendante du programme.
Une variable, ou un paramètre de fonction, peut avoir aucun, un, deux, ou les trois qualificateurs (certaines combinaisons n'auraient que peu de sens, mais sont autorisées).
Qualificateur 'const'
modifierLa classe const
ne déclare pas une vraie constante, mais indique au compilateur que la valeur de la variable ne doit pas changer. Il est donc impératif d'assigner une valeur à la déclaration de la variable, sans quoi toute tentative de modification ultérieure entrainera une erreur de la part du compilateur :
const int i = 0;
i = 1; /* erreur*/
En fait, le mot-clé const
est beaucoup plus utilisé avec des pointeurs. Pour indiquer qu'on ne modifie pas la valeur du pointeur qui contient l'adresse de l'objet pointé, il faut ajouter le mot-clé const de la sorte
:
void fonction( const char * pointeur )
{
pointeur[0] = 0; /* erreur*/
pointeur = "Nouvelle chaine de caractères";
}
Dans cet exemple, la modification de la valeur du pointeur lève une erreur. Pour indiquer que la valeur pointée par le pointeur est constante, il faut ajouter le mot-clé const
de la sorte :
char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !"; /* erreur*/
Encore plus subtile, on peut mélanger les deux déclarations précédentes :
const char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !"; /* erreur*/
pointeur[0] = 0; /* erreur*/
Cette dernière forme est néanmoins rarement usitée. En outre ce dernier exemple présente un autre problème qui est la modification d'une chaîne de caractères « en dur », qui sont la plupart du temps placées dans la section lecture seule du programme et donc inaltérables.
Attention const
n'est pas une protection réelle contre les changements de valeur, qu'elles proviennent d'un comportement indéfini, ou de constructions valides du C telle que :
const char lettre = 'A';
memset(&lettre, 'B', 1);
putchar(lettre); /* affiche B */
En outre certaines constructions du C même si elles n'indiquent pas être const
le sont effectivement.
int nombres[3] = {1, 2, 3};
int * autres_nombres;
nombres = autres_nombres; /* erreur à la compilation, l’adresse d'un tableau ne peut pas être modifié */
char * phrase = "Salut tout le monde !";
phrase[0] = 's'; /* erreur probable à l'exécution, une chaîne de caractères littérale ne peut pas être modifiée */
Qualificateur 'volatile'
modifierCe mot-clé sert à spécifier au compilateur que la variable peut être modifiée à son insu. Cela annule toute optimisation que le compilateur pourrait faire, et l'oblige à procéder à chaque lecture ou écriture dans une telle variable tel que le programmeur l'a écrit dans le code. Ceci a de multiples utilisations :
- pour les coordonnées d'un pointeur de souris qui seraient modifiées par un autre programme ;
- pour la gestion des signaux (voir Gestion des signaux) ;
- pour de la programmation avec de multiples fils d'exécution qui doivent communiquer entre eux ;
- pour désigner des registres matériels qui peuvent être accédés depuis un programme C (une horloge, par exemple), mais dont la valeur peut changer indépendamment du programme ;
- etc.
On peut combiner const
et volatile
dans certaines situations. Par exemple :
extern const volatile int horloge_temps_reel;
déclare une variable entière, qu'on ne peut modifier à partir du programme, mais dont la valeur peut changer quand même. Elle pourrait désigner une valeur incrémentée régulièrement par une horloge interne.
Qualificateur 'restrict'
modifierIntroduit par C99, ce mot-clé s'applique aux déclarations de pointeurs uniquement. Avec restrict, le programmeur certifie au compilateur que le pointeur déclaré sera le seul à pointer sur une zone mémoire. Cela permettra au compilateur d'effectuer des optimisations qu'il n'aurait pas pu deviner autrement. Le programmeur ne doit pas mentir sous peine de problèmes…
Par exemple :
int* restrict pZone;