I. Compléments et rappels▲
I-A. Les différentes classes de variable▲
Une variable déclarée à l'intérieur d'un bloc est appelée une variable locale. Elle n'est
connue qu'à l'intérieur de ce bloc. On dit que sa visibilité est limitée à ce bloc. Les arguments
effectifs d'une fonction sont également des variables locales à cette fonction.
Une variable déclarée à l'extérieur de tout bloc (donc de toute fonction) est dite globale. Elle
est donc visible de puis n'importe quelle fonction définie (dans le même fichier source !)
après sa déclaration. L'utilisation des variables globales réduit la lisibilité du code. Ne les
utilisez que lorsque cela est vraiment nécessaire.
Une variable globale est dite permanente (ou statique). Cela signifie que la mémoire utilisée
par cette variable est valide pendant toute la durée d'exécution du programme. Elle ne perd
donc jamais sa valeur.
A l'opposé les variables locales sont par défaut automatiques. Leur durée de vie est égale au
temps d'exécution du bloc. On peut rendre une variable locale permanente à l'aide du mot-clé
static. Par exemple :
#include <stdio.h>
void
f
(
void
);
int
main
(
)
{
f
(
);
f
(
);
f
(
);
return
0
;
}
void
f
(
)
{
static
int
i =
100
;
printf
(
"
i = %d
\n
"
, i);
i++
;
}
Dans cet exemple, i est une variable statique (permanente) initialisée à 100. La mémoire
utilisée par i est donc allouée avant même que le programme ne s'exécute et non à chaque
appel de la fonction. Son contenu est initialisé à 100. Seulement, puisqu'elle a été déclarée
dans f, elle ne sera connue que dans f. Mais i est tout sauf une variable « locale » (plus
précisément : une variable automatique) à la fonction (f).
Au premier appel de f, on a donc i = 100 (puisqu'elle a été initialisée avec la valeur 100, avant
même l'exécution du programme). Au deuxième appel, i = 101 et au troisième appel, i = 102.
I-B. Les expression constantes▲
Une expression constante est une expression dont la valeur peut être connue sans qu'on ait
même à exécuter le programme. Par exemple les constantes littérales, l'adresse d'une variable
statique, etc. sont des expressions constantes. Une expression constante peut également être
composée, par exemple : 1 + 1.
Attention, une variable déclarée avec le qualificateur const est une variable, pas une
expression constante !
I-C. Initialisation des variables▲
Le langage C permet d'initialiser une variable lors de sa déclaration.
Puisqu'une variable automatique n'est « activée » qu'au moment où l'on entre dans le bloc
dans lequel elle a été déclarée, ce qui a lieu pendant l'exécution, on peut initialiser une
variable automatique avec n'importe quelle expression et non forcément une expression
constante.
De l'autre côté, puisque la mémoire utilisée par une variable statique est allouée avant même
l'exécution, elle ne peut être initialisée qu'avec une expression constante.
Lorsqu'une variable statique est déclarée sans aucune initialisation, elle sera initialisée par le
compilateur lui-même à 0. A l'opposé, le contenu d'une variable automatique déclarée sans
initialisation est à priori indéterminé.
I-D. Les déclarations complexes▲
Nous avons déjà vu dans le tutoriel précédent comment créer de nouveaux types de données à l'aide du mot-clé typedef. On peut bien sûr exprimer un type sans l'avoir défini avec typedef. Pour obtenir le type d'un objet, il suffit de prendre sa déclaration, sans le point-virgule, et d'effacer l'identificateur. Par exemple, dans les déclarations suivantes :
int
n;
char
const
*
p;
double
t[10
];
n est de type int, p de type char const *, et t de type double [10] ( « tableau de 10 double »).
Dans le troisième cas, double [10] est bien un type, cependant un tel type ne peut pas être
utilisé dans une déclaration ou dans un typedef car les crochets doivent apparaître après
l'identificateur. Par contre il pourra figurer en argument de l'opérateur sizeof ou dans le
prototype d'une fonction par exemple.
Certains cas sont un peu plus délicats. Par exemple pour déclarer un tableau de 10 char *, on
écrira :
char
*
t[10
];
t est donc de type char * [10].
Or, comme nous le savons déjà, un tableau peut être implicitement converti en un pointeur vers son premier élément.
Donc dans l'exemple ci-dessus, t (qui est un tableau de char *)
peut être implicitement converti en un pointeur sur char * soit char **.
Dans un cas tel que :
int
t[5
][10
];
- t est de type : int [5][10].
- t[0], t[1], ... t[4] sont de type : int [10].
- t est donc, pour un pointeur, un pointeur sur int [10].
Si l'on devait déclarer une variable p de ce type, on raisonnerait sans doute comme suit :
- p est un pointeur sur un int [10]
- *p est donc un int [10]
d'où :
int
(*
p)[10
];
p est donc de type int (*)[10] !
Maintenant, en faisant :
p =
t;
p pointe sur t[0] (qui est un tableau de 10 int). De même, p + 1 pointe sur t[1], p + 2 sur
t[2] etc.
Evidemment, on peut aussi pointer sur t avec un simple int * (puisqu'un tableau, quelle que
soit sa dimension, est un groupement d'objets de même type) mais cela nécessite cette fois-ci
un cast et un peu plus d'habileté dans le calcul d'adresses bref, rien qui soit nouveau pour nous.
C'est d'ailleurs la seule solution lorsqu'on veut passer un tableau à plusieurs dimensions à une fonction
qui se veut d'être générique (en réalité, on utilisera plutôt un pointeur générique ...).