|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Telle la misérable petite fourmi traversant la savane immense et impitoyable de la connaissance, nous avançons dans notre découverte du langage C. Après avoir vu la structure des programmes, assimilé les types de base, ingurgité les types composés, souffert les pointeurs, nous en terminons avec tout ce qui concerne les variables. Nous parlerons des types énumérés, des classes de stockage et de quelques autres subtilités. Les types énumérés N'ayez aucune crainte, ils ne sont pas dangereux, surtout si vous ne les attaquez pas. Qu'est-ce qu'un type énuméré ? C'est un type de variable qui est défini par un ensemble de valeurs que vous donnez. Vous les énumérez. Exemple tout de suite :
Ceci définit un type dont les variables ne peuvent prendre que ces valeurs. C'est plus parlant pour celui qui écrit un programme (et pour ceux qui le lisent) d'écrire :
...que d'écrire :
...en ayant quelque part LE renseignement utile disant que "3" correspond à "vert". En général, on a toujours besoin de ce genre de renseignement et il faut le rechercher en permanence (heu... éteint, c'est combien déjà ?). Ici, plus de problème, tout est clair. Pour déclarer une variable "Feu" du type "Type_Feu", il faut écrire :
Il est possible, comme pour les structures, de déclarer des variables en même temps que le type. Par exemple :
Mais, pour la même raison (ça ne met pas en relief la déclaration de la variable (flag) par rapport à celle du type), il vaut mieux ici aussi l'éviter. Vous pouvez enfin donner une valeur aux constantes qui composent votre type. Par exemple, si cela vous arrange, vous pouvez écrire :
Mais attention, l'intervalle de valeurs couvrira toutes les valeurs entières jusqu'à 8. Voilà en effet la faille dans ce tableau idyllique : derrière ces valeurs que vous énumérez, le compilateur cache de banals entiers. Et, à part un léger contrôle de type, facilement évitable au demeurant, vous pouvez faire à peu près n'importe quoi. En particulier, une variable du type "Type_Feu" peut prendre n'importe quelle valeur entre 0 et 8, ce qui est contraire à la définition que vous donnez. Autre problème, le type énuméré est juste une notion abstraite. En d'autres termes, il est impossible d'afficher les valeurs littérales d'une variable de type "Type_Feu". C'est dommage car cela restreint beaucoup cette utilisation. Mais c'est compréhensible si l'on pense que ces valeurs cachent en réalité des entiers. Par exemple, si vous écrivez à l'écran la valeur de la variable "Feu", vous obtiendrez une valeur numérique allant de 0 (rouge) à 4 (éteint) mais en aucun cas les chaînes de caractères rouge à éteint. En résumé, on utilise guère les types énumérés, en tout cas, seulement quand on n'a pas besoin d'afficher leurs valeurs. Même dans ce cas, cela serait possible mais il faudrait utiliser une grille de codage et ça devient trop lourd. Finalement, même si on ne s'en sert pas très souvent, il faut les connaître car ça peut être très utile. Le mot-clef "typedef" Voici quelque chose de bien pratique. Ce mot-clef permet de donner un autre nom à un type de données. Tout de suite, un exemple très apprécié des fainéants :
Si vous avez déjà déclaré "TypeConstruit" en tant que structure, cette ligne vous permet de définir le nouveau type "Construit". Par conséquent, au lieu de déclarer vos structures par :
...vous n'avez qu'à écrire :
Évidemment, c'est plus court. Mais c'est peut-être moins parlant car ça ne dit pas clairement à celui qui lit le listing que "Machin" est une structure. Autre utilisation plus astucieuse (il faut dire "plus génie logiciel"), supposons que vous utilisez dans un programme des variables dont les valeurs vont de -4000000 à +4000000. Vous écrivez en Lattice, voyez que le type "int" est codé sur 32 bits, donc qu'il convient parfaitement et vous déclarez des centaines de variables, dans tous les fichiers de votre programme avec le type "int". Et puis, un de vos copains (plus pour longtemps...) vient vous voir et compile votre programme avec son Manx. C'est là que les choses se gâtent : "Hey mec, ce n'est pas cool, ton programme, il fait rien que de se planter !". "Normal", pensez-vous, "le Manx code les entiers sur 16 bits par défaut. Il suffit de remplacer les "int" dans mes fichiers par des "long". Misère ! J'en ai partout ! Des heures de travail pour porter mon programme sur Manx...". Sans compter que vous risquez d'oublier des modifications et donc, de vous retrouver avec les bons bogues des familles... Revenons quelques lignes en arrière et examinons le comportement du génilogiciéliste avisé. Il ne va pas déclarer ses variables du type "int" mais du type "Donnee", par exemple. Et écrire bien en vue dans son programme :
Si l'on veut ensuite changer le type "Donnee" en "long", pas de problème, il n'y a qu'un seul mot à changer. Autres exemples :
...déclare le type pointeur de caractères (donc aussi de chaînes). Vous pouvez donc écrire après :
...pour déclarer la chaîne toto.
...définit le type "tableau de dix caractères". Les classes de stockage Voici des notions extrêmement importantes pour sortir de l'ère de la bidouille et entrer dans celle de la programmation intelligente. Les classes de stockage sont la plus grande invention depuis celle de la touche F10. En effet, en jouant sur ces classes de stockage, vous pouvez augmenter la performance de vos programmes, diminuer leur taille, et surtout faciliter leur compréhension. Les classes de stockages définissent le statut de vos variables, la façon dont elles sont mémorisées. Elles sont au nombre de quatre :
Les variables automatiques (auto) C'est le nom utilisé en C pour parler des variables dynamiques. Si vous n'avez jamais vu ce mot dans un programme C, c'est parce qu'il est facultatif. Lorsque vous déclarez une variable dynamique (à l'intérieur d'une fonction), cette classe de stockage est donnée implicitement. Mais vous pourriez très bien écrire :
Cette classe de stockage correspond à une allocation dynamique de la variable entier. Nous en avons déjà parlé plusieurs fois donc je serai bref : une variable dynamique est réservée sur la pile à l'entrée de la fonction ; elle n'est connue du programme qu'à l'intérieur de cette fonction. Enfin, elle est détruite à la fin de l'exécution de cette fonction. Autrement dit, au prochain appel de la fonction, cette variable sera recréée mais il ne faut pas espérer y retrouver la valeur qu'elle avait lors du dernier appel (ça peut se produire éventuellement si vous appelez la même fonction deux fois de suite, mais programmer comme ça, c'est plus dangereux que de rouler sur l'autoroute à contresens, sans sa ceinture, avec deux grammes de sang par litre d'alcool). Naturellement, elle n'est pas connue des autres fonctions, même celles qui sont appelées par celle où elle se trouve. La classe static Elle désigne les variables statiques et correspond à deux utilisations différentes. 1. A l'intérieur d'une fonction, elle déclare une variable rémanente. C'est-à-dire que sa valeur est conservée d'un appel de la fonction à l'autre, mais cette variable est tout de même locale, connue uniquement de cette fonction. C'est l'inverse d'une variable automatique, que nous venons de voir ; la variable, bien que locale à la fonction, est ici statique. Deux exemples pour illustrer la différence :
(on aurait pu écrire : "auto int loto;", c'était strictement équivalent)
Si on appelle plusieurs fois de suite ces deux fonctions, que se passe-t-il ? Dans le premier cas, la variable "toto" est allouée, non initialisée. Sa valeur est incrémentée, puis affichée. Plusieurs appels successifs de cette fonction ne rendront pas une suite de valeurs croissantes : ça peut se produire mais rien n'est moins sûr puisque la variable est allouée et libérée à chaque appel de la fonction. Dans le deuxième cas, vous aurez à coup sûr droit à une suite croissante d'entiers car la variable "toto" est maintenant statique (ou rémanente). Sa valeur est conservée entre deux appels parce qu'elle n'est pas détruite à la fin de la fonction. On dit que c'est une variable privée. Pourquoi utiliser une variable locale à une fonction, on aurait pu la déclarer globale au programme (et donc statique aussi) ? me direz-vous. Effectivement, déclarer une variable locale statique n'est pas indispensable mais c'est un bon style de programmation. Car, si vous estimez que cette variable ne doit être connue que de cette fonction et d'aucune autre, c'est la solution à utiliser. Ça évitera ensuite toute interaction, erreur ou autre tracasserie lors de modifications ultérieures du programme. 2. Hors d'une fonction, cette classe de stockage déclare une variable locale à un fichier. Je m'explique : nous avons déjà vu que toutes les variables déclarées hors d'une fonction sont statiques. Sans le mot-clé static, elles sont globales au programme, alors qu'avec static, elles sont locales à un fichier. Nous y reviendrons en détail lorsque nous parlerons de compilation séparée, mais vous savez tous que le compilateur génère pour chaque fichier source un fichier objet avec le suffixe ".o". A partir d'un ou de plusieurs fichiers objets, l'éditeur de liens (linker) produit un exécutable. Une variable déclarée sans static, hors d'une fonction, est connue dans tout le programme, vous pouvez la nommer dans les autres fichiers le constituant (à une condition que nous verrons juste après). Une variable déclarée avec stade, hors d'une fonction, est ignorée par l'éditeur de liens et connue exclusivement dans le fichier où elle est déclarée. Vous ne pouvez pas l'utiliser dans les autres fichiers qui constituent le programme. Ça vous rappelle le principe de la variable privée d'une fonction ? Bravo, c'est un peu la même chose : la variable est ici privée, mais pour un fichier. Ici aussi, vous pouvez mettre en oeuvre un meilleur style de programmation en créant des modules, réalisant chacun une fonctionnalité de votre programme, et qui s'interdisent de savoir ce que font les autres. La classe extern Elle déclare des variables externes (si, si, sans blague). Nous avons vu qu'une variable globale, déclarée sans static, était globale au programme. Encore faut-il dire aux autres fichiers qui constituent le programme que cette variable existe. Cela se fait par :
...dans un des fichiers qui utilisent "toto" et :
...dans tous les autres fichiers qui ont besoin d'accéder à cette variable. Ainsi, dans tous ces derniers fichiers, le compilateur ne réserve pas la variable "toto" mais prend en note qu'il s'agit d'une variable externe. Il remplace donc son adresse par une étiquette. C'est l'éditeur de liens qui remplacera cette étiquette par l'adresse réelle de "toto", qui sera connue dans le fichier objet contenant la première des deux déclarations. En résumé, dans tous les fichiers qui doivent accéder à la variable "toto" sauf un, vous devez la déclarer avec extern. Il est possible cependant de ne pas déclarer comme externes les variables entières, car elles le sont par défaut. Mais je regrette de vous l'avoir dit car c'est le principe même de l'attrape-nigaud (et je suis resté poli). Si vous voulez écrire un générateur de bogues aléatoires, vous avez la formule. Conséquence : chaque fois que vous vous référez à une variable globale d'un autre fichier, déclarez-la avec :
S'il s'agit d'une fonction, il est sympathique, mais pas obligatoire, de déclarer le type de ses arguments. C'est utile parce que l'éditeur de liens ne fait aucune vérification là-dessus. Il est donc recommandé d'écrire :
La classe register C'est la classe registre. Elle est réservée exclusivement à certaines variables dynamiques. En sont donc exclues toutes les variables globales, externes et locales statiques. Plus d'autres que nous allons voir. Ce mot-clé indique au compilateur que le code qu'il doit générer doit stocker, dans la mesure du possible, cette variable dans un registre plutôt qu'en mémoire. Vous comprenez pourquoi les variables statiques n'y ont pas droit : elles existent tout au long du programme et leur nombre dépasse certainement celui des registres du microprocesseur. C'est pourquoi seules celles allouées à l'intérieur des fonctions (donc les locales dynamiques) ont le droit d'utiliser cette classe. Au lieu d'utiliser une variable allouée sur la pile, le programme utilise un registre. Ça peut permettre de gagner beaucoup de temps sur la manipulation d'indices de boucle, ou sur des calculs. Il faut noter que les paramètres de fonctions, en tant que variables locales dynamiques, rentrent eux aussi dans la catégorie des variables qui peuvent utiliser cette classe. Par contre, il faut en exclure les variables dont vous devez connaître l'adresse (par &toto, par exemple en paramètre d'une fonction). En effet, qui peut me dire quelle est l'adresse d'un registre ? Voici un exemple tout simple :
On peut rêver mieux en matière d'efficacité mais ça illustre bien les possibilités d'utilisation de ce mot-clé. Les instructions affectant les registres étant bien plus rapides que celles utilisant la mémoire, vous pouvez tirer de la classe register des gains de temps substantiels. Mais il convient de modérer l'enthousiasme qui est en train de vous gagner à la lecture de ces lignes... Pour deux raisons :
En résumé, sachez apprécier et consommer avec modération. Et voilà, c'est fini pour les variables. Le mois prochain, nous attaquerons les choses sérieuses et parlerons structures de contrôle.
|