| |
Une variable doit être définie par le programmateur dans une déclaration, où l'on indique le nom que l'on désire lui donner, son type (int, float,...) pour que le compilateur sache combien de mémoire il doit lui réserver et les opérateurs qui peuvent lui être associés, mais aussi comment elle doit être gérée (visibilité, durée de vie,...). Dans tout bloc d'instructions, avant la première instruction, on peut déclarer des variables. Elles seront alors "locales au bloc" : elles n'existent qu'à l'intérieur du bloc. Ces variables sont mises en mémoire dans une zone de type "pile " : quand, à l'exécution, on arrive sur le début du bloc ({), on réserve la mémoire nécessaire aux variables locales au sommet de la pile (ce qui en augmente la hauteur), et on les retire en quittant le bloc . L'intérêt consiste à n'utiliser, à un instant donné, que la quantité nécessaire de mémoire, qui peut donc resservir par après pour d'autres variables. Le désavantage (rarement gênant et pouvant être contrecarré par la classe STATIC ) est qu'en quittant le bloc (par } ou un branchement), et y entrant à nouveau plus tard (par son {), les variables locales ne sont plus nécessairement recréées au même endroit, et n'auront plus le même contenu. De plus la libération/réservation de la pile aura fait perdre un peu de temps. Par contre, lorsque l'on quitte temporairement un bloc (par appel à une fonction), les variables locales restent réservées. La sortie d'un bloc par un branchement gère la libération des variables locales, mais seule l'entrée dans un bloc par son { gère leur création. Exemple : #include <stdio.h> int doubl(int b) {int c; c=2*b; b=0; return(c); } void main(void) { int a=5; printf("%d %d\n",doubl(a),a); } A l'entrée du bloc main, création de a, à qui l'on donne la valeur 5. Puis appel de doubl : création de b au sommet de la pile, on lui donne la valeur de a. Puis entrée dans le bloc, création sur la pile de c, on lui donne la valeur b*2=10, on annule b (mais pas a), on rend 10 à la fonction appelante, et on libère le sommet de la pile (c et b n'existent plus) mais a reste (avec son ancienne valeur) jusqu'à la sortie de main. On affichera donc : 10 5. Une variable locale est créée à l'entrée du bloc, et libérée à la sortie. Cette période est appelée sa durée de vie . Mais pendant sa durée de vie, une variable peut être visible ou non. Elle est visible : dans le texte source du bloc d'instruction à partir de sa déclaration jusqu'au }, mais tant qu'une autre variable locale de même nom ne la cache pas. Par contre elle n'est pas visible dans une fonction appelée par le bloc (puisque son code source est hors du bloc).
autre exemple : void main(void); {int a=1; [1] {int b=2; [2] {int a=3; [3] fonction(a); [4] } [5] fonction(a); [6] } [7] } [8] int fonction (int b) [a] {int c=0; [b] c=b+8; [c] } [d] analysons progressivement l'évolution de la pile au cours du temps (en gras : variable visible) :
[1] a=1
[2] a=1 | b=2
[3] a=1 | b=2 | a=3 : seul le a le plus haut est visible (a=3), l'autre vit encore (valeur 1 gardée) mais n'est plus visible.
[4a] a=1 | b=2 | a=3 | b=3 : entrée dans la fonction, recopie de l'argument réel (a) dans l'argument formel (b). Mais a n'est plus visible.
[4b] a=1 | b=2 | a=3 | b=3 | c=0
[4c] a=1 | b=2 | a=3 | b=3 | c=11 : quand le compilateur cherche la valeur de b, il prend la plus haute de la pile donc 3 (c'est la seule visible), met le résultat dans le c le plus haut. L'autre b n'est pas modifié.
[4d] a=1 | b=2 | a=3 : suppression des variables locales b et c du sommet de la pile
[5] a=1 | b=2 : sortie de bloc donc libération de la pile
[6a] a=1 | b=2 | b=1 : l'argument réel (a) n'est plus le même qu'en [4a]
[6b] a=1 | b=2 | b=1 | c=0
[6c] a=1 | b=2 | b=1 | c=9
[6d] a=1 | b=2 : suppression b et c
[7] a=1
[8] la pile est vide, on quitte le programme Notez que la réservation et l'initialisation prennent un peu de temps à chaque entrée du bloc. Mais ne présumez jamais retrouver une valeur sur la pile, même si votre programme n'a pas utilisé la pile entre temps (surtout sur système multitâche ou avec mémoire virtuelle). Une déclaration a toujours la structure suivante : [classe] type liste_variables [initialisateur]; (entre [] facultatif) Le type peut être simple (char, int, float,...) ou composé (tableaux, structures..., voir plus loin). La liste_variables est la liste des noms des variables désirées, séparées par des virgules s'il y en a plusieurs. Chaque nom de la liste peut être précédés d'une *, ceci spécifiant un pointeur . L' initialisateur est un signe =, suivi de la valeur à donner à la variable lors de sa création (à chaque entrée du bloc par exemple). La valeur peut être une constante, mais aussi une expression avec des constantes voire des variables (visibles, déjà initialisées). La classe peut être :
- auto (ou omise, c'est la classe par défaut pour les variables locales) : la variable est créée à l'entrée du bloc (dans la pile) et libérée automatiquement à sa sortie (comme expliqué plus haut).
- register : la variable est créée, possède la même durée de vie et visibilité qu'une classe auto, mais sera placée dans un registre du (micro)processeur. Elle sera donc d'un accès très rapide. Si tous les registres sont déjà utilisés, la variable sera de classe auto. Mais le compilateur peut avoir besoin des registres pour ses besoins internes ou pour les fonctions des bibliothèques, s'il n'en reste plus le gain peut se transformer en perte. De plus les compilateurs optimisés choisissent de mettre en registre des variables auto, et souvent de manière plus pertinente que vous. Mais le compilateur ne sait pas à l'avance quelles fonctions seront appelées le plus souvent, dans ce cas une optimisation manuelle peut être utile, par exemples dans le cas des éléments finis où une même instruction peut être répétée des milliards de fois.
- static : la variable ne sera pas dans la pile mais dans la même zone que le code machine du programme. Sa durée de vie sera donc celle du programme. Elle ne sera initialisée qu'une fois, au début du programme, et restera toujours réservée. En retournant dans un bloc, elle possédera donc encore la valeur qu'elle avait à la précédente sortie. Sa visibilité reste la même (limitée au bloc). Une variable statique permet en général un gain en temps d'exécution contre une perte en place mémoire.
Une déclaration faite à l'extérieur d'un bloc d'instructions (en général en début du fichier) est dite globale. La variable est stockée en mémoire statique, sa durée de vie est celle du programme. Elle est visible de sa déclaration jusqu'à la fin du fichier. Elle sera initialisée une fois, à l'entrée du programme (initialisée à 0 si pas d'autre précision). Le format d'une déclaration globale est identique à une déclaration locale, seules les classes varient. Par défaut, la variable est publique, c'est à dire qu'elle pourra même être visible dans des fichiers compilés séparément (et reliés au link). La classe static , par contre, rend la visibilité de la variable limitée au fichier actuel. La classe extern permet de déclarer une variable d'un autre fichier (et donc ne pas lui réserver de mémoire ici, mais la rendre visible). Elle ne doit pas être initialisée ici. Une variable commune à plusieurs fichiers devra donc être déclarée sans classe dans un fichier (et y être initialisée), extern dans les autres. Toute fonction, pour pouvoir être utilisée, doit également être déclarée. Une déclaration de fonction ne peut être que globale, et connue des autres fonctions. Une déclaration de fonction est appelée "prototype ". Le prototype est de la forme : [classe] type_retourné nom_fonction(liste_arguments); elle est donc identique à l'entête de la fonction mais :
- est terminée par un ; comme toute déclaration
- les noms des arguments n'ont pas besoin d'être les mêmes, il peuvent même être omis (les types des arguments doivent être identiques). Sans précision de classe, la fonction est publique. Sinon, la classe peut être extern (c'est ce que l'on trouve dans les fichiers .H) ou static (visibilité limitée au fichier). Le prototype peut être utilisé pour utiliser une fonction du même fichier, mais avant de l'avoir définie (par exemple si l'on veut main en début du fichier). En général, lorsque l'on crée une bibliothèque (groupe de fonctions et variables regroupées dans un fichier compilé séparément), on prépare un fichier regroupant toutes les déclarations extern, noté .H, qui pourra être inclus dans tout fichier utilisant la bibliothèque. exemples de déclarations globales :
int i,j; /* publiques, initialisées à 0 */
static int k=1; /* privée, initialisée à 1 */
extern int z; /* déclarée (et initialisée) dans un autre fichier */
float produit(float,float); /* prototype d'une fonction définie plus loin dans ce fichier */
extern void échange(int *a, int *b); /* prototype d'une fonction définie dans un autre fichier */ Avant la norme ANSI, le prototype n'existait pas. Une fonction non définie auparavant était considérée comme rendant un int (il fallait utiliser un cast si ce n'était pas le cas). La norme ANSI permet de définir de nouveaux types de variables par typedef. structure : typedef type_de_base nouveau_nom; Ceci permet de donner un nom à un type donné, mais ne crée aucune variable. Une déclaration typedef est normalement globale et publique. exemple :
typedef long int entierlong; /* définition d'un nouveau type */
entierlong i; /* création d'une variable i de type entierlong */
typedef entierlong *pointeur; /* nouveau type : pointeur = pointeur d'entierlong */
pointeur p; /* création de p (qui contiendra une adresse), peut être initialisé par =&i */ Remarque : Le premier typedef pouvait être remplacé par un #define mais pas le second.
[Precédent] [Sommaire] [Suivant] [Haut] |
|