Accueil > Programmation Cocoa > La double vie des variables

La double vie des variables

Par Seth Roby le 07/10/2003

Traduit par Olivier, le 22/03/2004

Quand Batman revient à la maison à la fin d’une soirée passée à combattre le crime, il revêt son costume et sa cravate et devient Bruce Wayne. Quand Clark Kent voit qu’une nouvelle information est très louche, il se cache dans une cabine téléphonique pour se changer en Superman. Quand vous programmez, toutes les variables avec lesquelles vous travaillez font de même puisqu’elles vous présentent une de leur facette, et en présentent une autre à la machine.

Ces identités secrètes ont différents rôles et elles nous aident à comprendre comment fonctionnent les variables.
Dans cette leçon, nous allons écrire un peu moins de code que dans les articles précédents, mais nous allons voir en détails comment fonctionnent les variables.

Le Fantôme dans la Machine

La dualité la plus évidente qui existe avec les variables est la façon dont le programmeur les voit, qui est totalement différente de celle de l’ordinateur.
Quand vous tapez dans Project Builder, vos variables sont des mots normaux mis ensemble.
Vous travaillez avec elles à ce niveau, les déplaçant comme bon vous semble.

Cependant, quand la machine compile votre code, elle effectue un peu de traduction.
Lors de l’exécution, l’ordinateur ne voit que des 1 et de 0, qui est tout ce que peut comprendre un ordinateur : une chaine continue de nombres binaires qu’il peut interpréter de différentes manières.

Ce concept est important à comprendre en programmation C, spécialement dans l’architecture RISC du Mac.
Quasiment toutes les variables avec lesquelles vous travaillez peuvent être représentées en mémoire sur 32 bits : trente deux 1 et 0 représentent l’information contenue dans une variable.
Il y a des exceptions, comme avec les nouveaux G5 64-bits, et dans le monde Altivec et ses mots de 128-bits ; mais la plupart du temps quand nous traitons avec des variables comme int et les autres types que nous allons voir dans la leçon, nous allons utiliser des mots identifiant ces blocs de trente deux 1 et 0.

Comprendre l’idée de base vous permettra d’accéder à un pouvoir immense dont on peut user voire abuser, et nous allons voir quelques manières de l’utiliser dans cet article.

La Vie d’une Variable

Une variable vit une vie simple, très active, mais courte (qui peut se mesurer en nanosecondes généralement).
Tout commence quand le programme en cours d’exécution rencontre une déclaration de variable, qu’il met alors au monde.
Il existe deux zones dans lesquelles une variable puisse vivre, mais nous évoquerons cela plus tard.

Cette variable est ensuite utilisée par différentes lignes de code, conservant les valeurs qui lui sont passées lors des différents assignements.
Au cours de sa vie, une variable peut conserver n’importe quel nombre de variables et peut être utilisées de différentes manières de façon illimitée.
Cette flexibilité s’appuie sur le précepte que nous venons juste d’apprendre : une variable est en fait juste un bloc de bits, et ces bits peuvent contenir n’importe quelle donnée dont le programme à besoin de se rappeler.
Elle peuvent contenir un entier aussi petit que -2 147 483 647 jusqu’à 2 147 483 647 (un de moins que plus ou moins 2^31).
Elles peuvent se rappeler d’un caractère.
Elles peuvent contenir un nombre décimal avec une une très grande précision.
Elles peuvent contenir une date d’il y a plusieurs siècles à la seconde près.
Personne n’a le droit de se moquer de quelques bits.

Quand une variable a terminé son travail, elle ne part pas en retraite, et on ne lui fait plus jamais appel.
La variable cesse simplement d’exister, et les trente deux bits qu’elle stockait sont libérés, pour pouvoir éventuellement être utilisés par une autre variable plus tard.

Mais les variables bénéficient d’une chose que les personnes n’ont pas ; leur fin est toujours connue avec précision.
Toute variable est déclarée à l’intérieur d’un bloc de code, et ce bloc détermine sa durée de vie.
Quand se bloc se referme, les variables qui y étaient déclarées sont libérées.
Si le bloc dans lequel elles sont déclarées contient d’autres blocs, les variables y sont visibles.
Cette relation hiérarchique est simple à suivre et la durée de vie d’une variable s’appelle sa visibilité.

Nous pouvons voir un exemple de ceci dans le code que nous avons déjà écrit.
Dans chaque bloc de fonction, nous déclarons des variables qui contiennent nos données.
Quand chaque fonction se termine, l’espace mémoire des variables qui y ont été déclarées est mis à disposition de l’ordinateur.
Les variables vivent dans les blocs de conditionnelles et boucles que nous écrivons, mais pas à l’intérieur des fonctions que nous appelons, parce que celles-ci ne sont pas des sous-blocs, mais des sections de code à part entière.

Mais quelques variables sont immortelles.
Ces variables sont déclarées en dehors des blocs, des fonctions.
Puisqu’elles n’appartiennent à aucun bloc, elles sont appelées variables globales (en opposition aux variables locales), parce qu’elles appartiennent à tous les blocs, et leur visibilité est sans fin.
Bien que très puissantes, ce genre de variables est généralement à éviter, parce qu’elles encouragent une mauvaise conception de code.

Stack / Pile

Plus haut, j’ai mentionné que les variables peuvent vivre en deux endroits différents.
Nous allons examiner ces deux zones l’une après l’autre, et nous allons commencer par celle appelée la Stack (Pile).
Comprendre la pile permet de comprendre comment fonctionnent les programmes, ainsi que la visibilité.

La pile est juste comme son nom l’indique : une tour qui se commençe par le bas et s’élève vers le haut au fur et à mesure de sa construction.
Dans notre cas, les éléments de la pile sont appelés “Stack Frames” ou plus simplement “frames”.
On démarre tout en bas avec une première frame, et on construit à partir de là.

Chaque frame représente une fonction.
La frame du bas est toujours la fonction main, les frames au-dessus sont les fonctions qu’elle appelle.
A n’importe quel moment, la stack peut vous montrer le chemin par lequel est passé votre code pour arriver jusqu’où il est.
La frame du haut représente la fonction que le code est en train d’exécuter, est la frame juste en-dessous est celle de la fonction qui a appelé la fonction courante et ainsi de suite, jusqu’à la fonction main, qui est le point de départ de tout programme C.

A l’intérieur de chaque frame il y a plein d’informations utiles.
Elles indiquent à l’ordinateur quel code est en cours d’exécution, où aller ensuite, où aller dans le cas d’une instruction return, et plein d’autres choses très utiles pour l’ordinateur, mais beaucoup moins pour vous, en général.
Une des choses qui peut vous être utile est la partie de la frame qui suit toutes les variables que vous utilisez.
Donc un des premiers endroits où peut vivre une variable est la pile.
C’est un endroit très agréable où vivre, dans lequel toutes les créations et destructions d’espace sont gérées pour vous par les frames qui sont créées et détruites.
Il est très rare que vous ayez à vous inquiéter de faire de la place pour les variables dans la pile.
Le problème est que la durée de vie des variables dépend de celle de la frame, c’est-à-dire le temps de la fonction qui les a déclarées.
C’est souvent suffisant, mais que faire quand vous devez conserver l’information plus longtemps ?

Heap / Tas

Pour répondre à ce problème, nous nous tournons vers la deuxième zone où peuvent exister les variables, qui est appelé Heap (Tas).
Si vous imaginez la pile comme un immeuble tout en hauteur, les appartement s’empilant les uns sur les autres, et les variables comme ses occupants, alors le tas est plutôt comme une banlieue urbaine où chaque habitant trouve sa place, sur un lot d’une surface de différente taille, et à des endroits impossible à prédire d’avance.
Comparé à la simplicité offerte par la pile, le tas semble positivement chaotique, mais en réalité, chacun obéit à ses propres règles.

Comparé à la pile, le tas est simple à comprendre.
Toute la mémoire restante est “dans le tas” (sauf quelques zones réservées).
Il y a très peu de structure, mais en contrepartie de cette liberté de mouvement, vous devez créer et détruire les contours de votre espace.
Et il y toujours la possibilité que le tas n’ait pas l’espace dont vous avez besoin.

Un Pointeur Entre Deux Mondes

Puisque le tas n’a pas de règles définit par rapport à l’endroit où il va construire un espace pour vous, il doit y avoir un moyen de savoir où se trouve votre nouvel espace.
Et la réponse est, toute simple, l’adressage.
Quand vous créez un nouvel espace dans le tas pour vos données, vous obtenez une adresse qui vous indique où se trouve votre nouvel espace, où vos bits de données peuvent aller.
Cette adresse est appelée un pointeur, et c’est en fait juste un nombre hexadécimal qui pointe sur une zone du tas.
Puisqu’il s’agit en réalité d’un simple nombre, il peut être facilement stocké dans une variable.

Voyons un exemple en convertissant notre variable de pile favoriteNumber en variable de tas.
La première chose que nous allons faire est de trouver le projet sur lequel nous avons travaillé et l’ouvrir dans Project Builder.
Dans le fichier <main.c>, nous commençons à travailler de haut en bas.
Sous la ligne :

#include <stdio.h>

insérez

#include <libc.h>

Ceci va nous permettre d’utiliser quelques fonctions auxquelles nous n’avions pas accès auparavant.
Ces lignes sont encore un mystère pour le moment, mais nous les expliquerons bientôt.
Pour le moment, nous allons travailler avec la fonction main, dans laquelle favoriteNumber est déclarée et utilisée.
La première chose que nous devons modifier est la manière de déclarer la variable.
Au lieu de :

	int favoriteNumber = (3 * 4) / 2;

Nous allons avoir deux lignes :

    int* favoriteNumber = malloc(sizeof(int));
    *favoriteNumber = (3 * 4) / 2;

Notez tout dabord que le type de favoriteNumber a changé.
Au lieu de notre int bien familier, nous utilisons maintenant int*.
Ici, l’astérisque est un opérateur.
Vous vous souvenez que nous utilisons également l’astérisque comme signe de multiplication.
La position de l’astérisque change sa signification.
Cette opérateur veut en fait dire “voici un pointeur”.
Ici, il indique que favoriteNumber ne sera pas un int mais un pointeur sur un int.
Et au lieu de simplement dire que nous allons utiliser cet int, nous avons une étape supplémentaire pour créer l’espace nécessaire, ce que fait malloc().
Cette fonction prend un argument qui spécifie la taille de l’espace nécessaire et ensuite retourne un pointeur sur cet espace.
Nous lui avons passé le résultat d’une autre fonction, sizeof, à laquelle nous avons passé le type int.
En réalité, sizeof est une macro, mais pour l’instant cela ne nous intéresse pas : tout ce dont nous avons besoin de savoir est qu’elle nous indique la taille de tout ce que nous lui passons, et dans ce cas, int.
Donc une fois que malloc() a terminé, elle nous retourne une adresse dans le tas où nous pouvons placer un entier.
Il est important de vous rappeler que les données sont stockées dans le tas, mais les adresses de ces données sont stockées dans une pointeur dans la pile.

Notre ligne suivante semble familière, excepté qu’elle commence par un astérisque.
Nous utilisons encore une fois cette opérateur, ce qui nous permet de remarquer que la variable avec laquelle nous travaillons est un pointeur.
Si nous ne l’avions pas précisé, l’ordinateur essaierait de mettre les résultats de la partie droite de l’instruction dans le pointeur, écrasant la valeur qui nous intéresse dans ce pointeur, qui est une adresse.
De cette manière, l’ordinateur sait qu’il doit placer les données non pas dans le pointeur, mais à l’endroit indiqué par le pointeur, c’est à dire dans le tas.
Après cette ligne, notre int vit joyeusement dans le tas, contenant une valeur de 6, et notre pointeur nous indique où la donnée se trouve.

Prenons un instant pour revoir tout cela.
Nous avons créé deux variables.
La première se trouve dans le tas et stocke des informations.
C’est la plus évidente.
Mais la seconde variable est un pointeur sur la première, et elle existe dans la pile.
Et c’est cette variable que nous avons appelée favoriteNumber, et c’est celle avec laquelle nous travaillons.
Il est important de se rappeler que maintenant, il y a deux parties à notre variable, chaque partie existant dans un monde différent.
Cette division est fréquente en C, mais omniprésente en Cocoa.
Quand vous commencez à créer des objets, Cocoa les crée tous dans le tas, parce que la pile n’est pas assez grande pour tous les contenir.
En Cocoa, vous manipulez les objets à travers des pointeurs partout et vous n’avez pas la possibilité de travailler directement avec.

Le reste de notre conversion est de la même veine.
Au lieu d’aller de ligne en ligne, comparons les résultats finaux : quand la transition est complète, l’ancien code :

int main (int argc, const char * argv[])
{
    //Calcule notre nombre favori
    int favoriteNumber = (3 * 4) / 2; //est-ce-que quelqu'un n'a pas pour nombre favori
                                      //un entier ?
    favoriteNumber = integerForSeedValue(favoriteNumber + 2);

    /* maintenant indiquons au monde entier
        quel est notre nombre favori */
    countTo(favoriteNumber);

	return 0;
}

Devrait maintenant ressembler à :

int main (int argc, const char * argv[])
{
//Computes our favorite number
    int* favoriteNumber = malloc(sizeof(int));
    *favoriteNumber = (3 * 4) / 2;
    *favoriteNumber = integerForSeedValue(*favoriteNumber + 2);

    /* maintenant indiquons au monde entier
        quel est notre nombre favori */
    countTo(*favoriteNumber);

    free(favoriteNumber);
    return 0;
}

Vous remarquerez l’astérisque à chaque fois que nous faisons appel à favoriteNumber, excepté pour cette nouvelle ligne juste avant return.

Il s’agit d’une nouvelle fonction pour gérer le tas.
Après avoir créé un espace dans le tas, il est à vous jusqu’à ce que vous le rendiez.
Quand votre programme à terminé avec cet espace, vous devez indiquer à l’ordinateur de façon explicite que vous n’en avez plus besoin sinon, l’ordinateur le préservera ( jusqu’à ce que le programme s’arrête).
L’appel à free() indique simplement à l’ordinateur que vous n’avez plus besoin de cet espace et qu’il peut le réutiliser pour autre chose.

Ce code devrait compiler et s’exécuter correctement, et vous ne devriez voir aucun changement dans les résultats.
Alors pourquoi avons nous fait tout cela ?

Pour ce programme, c’était un peu trop. C’était en fait vraiment exagéré.
Il n’y a en général aucune raison pour stocker des entiers dans le tas, à moins que vous n’en créiez vraiment beaucoup.
Mais même dans cette simple forme, cela nous donne un peu plus de flexibilité que nous n’en avions auparavant, en cela que nous pouvons créer et détruire les variables selon nos besoins, sans nous inquiéter de la pile.
Cela nous a également présenté un nouveau type de variable, le pointeur, que vous utiliserez de façon intensive dans vos programmes.
Et c’est un type extrèmement utilisé en Cocoa, donc il était nécessaire que vous le compreniez, même si Cocoa le masque plus qu’il ne l’est ici.

free(lecteur)

Cela nous donne une bonne base pour mieux comprendre les variables, et c’est ce que nous examinerons dans la prochaine leçon.
Ces nouveaux types de variables que j’ai promis de voir la dernière fois apparaitront enfin, et nous verrons quelques concepts pour organiser nos données de façon plus structurée, une sorte de précurseur des objets avec lesquels travaille Cocoa.
Et nous verrons quelques trucs sympathiques qu’il est possible de faire avec les bits.

Textes originaux en anglais sur O’Reilly : The Double Life of Variables par Seth Roby

opoppon Programmation Cocoa , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire