Les branchements IV
Arguments et registres
|
Leçon 13 : Passage des paramètres d'une fonction
Utilisation des registres
Il faut être honnête, le passage des paramètres n'est
pas toujours aussi aisé qu'il a été montré
jusqu'ici. Cela n'est pas dû à FutureBASIC qui nous rend
au contraire les choses plus simples, mais à l'architecture des
processeurs. Si vous compilez votre programme pour le PowerPC, FutureBASIC
utilisera des registres du processeur (des cases spéciales) dans
lesquelles il y rangera des valeurs. L'intérêt de ces registres
est tout simplement de permettre au processeur de travailler beaucoup
plus rapidement, car accéder aux variables dans des registres demande
moins de temps que d'aller les retrouver dans la mémoire vive de
l'ordinateur. Lorsque l'on doit exécuter de longues boucles, la
différence est perceptible. Aussi, bien que l'utilisation des registres
soit optionnelle, vous ne manquerez pas de vouloir en profiter vous aussi.
Les variables placées ainsi en registre n'ont pas d'adresse en
mémoire et on commence à entrapercevoir les problèmes :
comment travailler sur des adresses-mémoire avec des variables
qui n'en ont pas ?
S'ajoute à cette difficulté pressentie, le fait que le Compilateur
stockera toutes les variables qu'il peut ranger dans des registres au
moment où une fonction est appelée jusqu'à ce qu'il
n'y ait plus un seul registre disponible. Il va commencer par y ranger
les paramètres qui sont passés à la fonction à
l'exclusion des chaînes de caractères et des variables en
simple et double précision. Il y rangera ensuite les variables
locales déclarées à l'intérieur de la fonction
si elles ne demandent pas plus de 4 octets de stockage (taille maximum
qui peut être contenue dans un registre), jusqu'à épuisement
des 8 registres utilisateur disponibles, après quoi, toutes
les variables locales suivantes sont stockées en mémoire.
Notez dès à présent que les variables à virgule
flottante disposent de registres particuliers; le programmeur peut contrôler
la mise en registre de ce type de variable de la façon suivante :
LOCAL
DIM AS DOUBLE x,y
LOCAL FN maFonction
DIM AS DOUBLE w,z
END FN
Les variables x et y
déclarées entre les mots-clés LOCAL
et LOCAL FN seront placées dans les
registres pour les flottants, tandis que les variables w
et z déclarées dans le corps
de la fonction seront localisées en mémoire adressable.
Bon nombre de commandes FutureBASIC, mais aussi d'appels à la Toolbox
et pourquoi pas de vos fonctions-utilisateur auront besoin de modifier
directement les valeurs de variables qui leur sont passées en paramètres,
de ce fait, les variables concernées doivent avoir une adresse
en mémoire, c'est-à-dire un emplacement où la fonction
pourra stocker une nouvelle valeur le cas échéant. Lors
de la compilation de votre programme, FutureBASIC détecte si une
variable placée en registre est utilisée dans une instruction
qui attend une variable nécessitant une adresse en mémoire.
Il vous signalera l'erreur et arrêtera la compilation. Le remède
le plus immédiat à cette situation est de forcer le Compilateur
à créer la variable en mémoire adressable, ce qui
se fait très simplement avec l'instruction DIM.
LOCAL FN maFonction
DIM @ x AS SHORT
DIM @ y AS HANDLE
DIM z AS SHORT
DIM w AS LONG
DIM r AS RECT
// instructions
END FN
Grâce au symbole @ dans les instructions
ci-dessus, les variables x et y
sont créées en mémoire adressable, tandis que z
et w seront placées dans des registres,
s'il en reste de disponibles. Les variables de type record ne sont jamais
placées dans des registres, donc dans l'exemple ci-dessus, la variable
r (ici une structure RECT)
n'aboutira jamais dans un registre.
L'ordre dans lequel le Compilateur rencontre dans votre code source les
variables à créer a une importance dans la mise en registre
des variables. Les paramètres de la fonction sont d'abord placés
en registre, ensuite viennent les variables déclarées par
l'instruction DIM. Il est donc conseillé
de déclarer au plus tôt les variables qui sont le plus souvent
sollicitées dans le corps de la fonction.
Supposez que vous vouliez écrire une fonction qui modifie la valeur
d'une variable que vous lui passez en paramètre depuis une autre
fonction locale, par exemple :
LOCAL FN ajouterUn(@pointeurVariable AS
PTR)
pointeurVariable.nil%++
END FN
LOCAL FN tester
DIM AS INT x, i
FOR i = 30 TO 20 STEP -1
FN AjouterUn(x)
PRINT x
NEXT
END FN
// Programme principal
FN tester
Ce petit bout de code recèle deux pièges auxquels vous serez
très souvent confrontés. Tout d'abord, la variable x
sera créée dans un registre, en conséquence le Compilateur
ne pourra pas faire son travail pour la fonction ajouterUn
puisqu'il lui sera impossible de résoudre l'adresse en mémoire
de la variable. Vous aurez donc droit à un message d'erreur, vous
signalant que la variable x ne peut pas être
placée en registre. On a vu qu'il était simple de régler
le problème, il suffit d'utiliser le symbole @
avec l'instruction DIM, éventuellement
de la manière suivante :
DIM AS INT i, @
x
Notez simplement que dans l'instruction ci-dessus, la variable i
sera placée en registre, tandis que x
sera créée en mémoire adressable et que si vous déclarez
d'autres variables sur la même ligne d'instruction après
la variable x, elles seront également
placées en mémoire.
Ce code acceptera de compiler, mais ne vous donnera peut-être pas
le résultat que vous escomptez. C'est l'occasion de revoir ici
que déclaration de variable et initialisation sont deux choses
bien distinctes. La déclaration réserve une place en mémoire
(ou bien réserve un registre) mais n'altère pas la valeur
précédemment contenue à cette adresse (ou dans ce
registre), si bien que la variable peut contenir toute valeur qui "traînait"
là avant sa création. Il faut donc expressément donner
une valeur initiale à votre variable x,
c'est-à-dire l'initialiser pour obtenir des valeurs prédictibles.
Pourquoi FutureBASIC n'initialise-t-il pas à 0 toutes les variables
nouvellement créées comme il le fait avec les variables
globales ?
L'initialisation des variables consomme du temps. Bien sûr, dans
une fonction appelée de manière isolée qui pourrait
éventuellement manipuler un grand nombre de variables locales,
cela ne serait pas vraiment perceptible, mais si maintenant vous devez
appeler des centaines de milliers de fois des dizaines de fonctions comportant
des dizaines de variables dans une boucle de votre programme, vous commencerez
à percevoir la différence. Notez que les variables globales
n'ont besoin d'être initialisées qu'une seule fois au démarrage
du programme et que FutureBASIC s'en charge en mettant toutes les variables
globales à 0. Dans beaucoup d'applications, pour ne pas dire la
majorité, la rapidité maximale est souvent ce qui est recherchée.
Deux possibilités s'offrent à vous pour régler le
problème de l'initialisation des variables locales :
- vous pouvez forcer FutureBASIC à initialiser à leur valeur
nulle toutes les variables qui seront utilisées dans le corps de
la fonction à l'aide du mot-clé CLEAR :
CLEAR LOCAL FN maFonction
END FN
Ou encore :
CLEAR LOCAL
LOCAL FN maFonction
END FN
Ceci est valable pour tous les types de variables y compris les chaînes
de caractères et les records.
- vous pouvez vous assurer que le flux du programme passera nécessairement
par une instruction qui assignera une valeur connue aux variables utilisées.
Cela revient à initialiser manuellement et individuellement chacune
des variables. Dans notre exemple, il nous suffirait de donner une valeur
quelconque (et pourquoi pas 0 ?) à notre variable x avant
de l'utiliser dans des instructions qui vont tenir compte de sa précédente
valeur. Voici donc le programme corrigé :
LOCAL FN ajouterUn(@pointeurVariable AS
PTR)
pointeurVariable.nil%++
END FN
LOCAL FN tester
DIM AS INT i,@ x
x = 0
FOR i = 30 TO 20 STEP -1
FN ajouterUn(x)
PRINT x
NEXT
END FN
// Programme principal
FN tester
Il est important de bien garder à l'esprit cette différence
entre la déclaration et l'initialisation, car vous pourriez être
chanceux et avoir un programme fautif qui semblera fonctionner correctement
pendant un certain temps, puis tout d'un coup se mettra à produire
des résultats inattendus en vous laissant perplexe pour un long
moment.
Dans l'exemple ci-dessus, nous retrouvons la forme syntaxique "adresse+point+décalage+identificateur
de type" que nous avons déjà expliquée. Ici,
nous utilisons le symbole % puisque nous
voulons ranger une valeur sur un entier court. Notez toutefois que FutureBASIC
dispose d'une commande qui incrémente d'une unité la variable
que vous lui passez en paramètre INC(variable)
qui est équivalent à variable = variable
+ 1. Le langage dispose également de mots-clés spécifiques
lorsque vous manipulez des adresses plutôt que des variables :
INC BYTE(adresse), INC
WORD(adresse) et INC LONG(adresse)
qui se passent de commentaires. Bien entendu DEC
BYTE, DEC WORD et DEC
LONG sont aussi disponibles. Mais, sachez que la forme d'écriture
que nous avons précédemment employée vous permet
de manipuler de même des nombres à virgule flottante et des
chaînes de caractères en utilisant le suffixe adéquat,
c'est pourquoi nous la privilégierons. Exemple :
LOCAL FN AssignerChaine(@pointeurVariable
AS PTR)
pointeurVariable.nil$ = "Je
suis une chaîne"
END FN
LOCAL FN tester
DIM AS STR255 variable
FN AssignerChaine(variable)
PRINT variable
END FN
// Programme principal
FN tester
Le passage des paramètres aux fonctions est une notion à
comprendre très tôt pour se sentir parfaitement à
l'aise avec FutureBASIC, malheureusement, il recèle encore quelques
difficultés pour arriver à la maîtrise.
On a vu que les paramètres sont, a priori, passés
par valeur. C'est-à-dire que si vous passez une variable en paramètre
à une fonction, c'est son contenu (sa valeur) qui est transmis.
En réalité, ceci n'est vrai que pour les variables courantes
qui ont jusqu'à 4 octets de stockage, mais FutureBASIC s'arrange
pour que ce principe s'applique également aux chaînes de
caractères et aux nombres à virgule flottante. Les records,
les containers et les tableaux ne bénéficient pas de ce traitement
de faveur et FutureBASIC transmet systématiquement l'adresse de
telles variables lorsque vous les utilisez dans la liste des paramètres
des appels à une fonction. Prenons le cas le plus simple, vous
souhaitez passer une variable record de type RECT
à une fonction locale :
DIM r AS RECT
r.top = 10 : r.left
= 10 : r.bottom = 50 : r.right
= 50
FN afficheRectangle(r)
FutureBASIC ne peut pas transmettre le contenu d'une telle structure,
ce qu'il va faire alors c'est transmettre de lui-même l'adresse
de la variable à la place. Cela a deux conséquences :
tout d'abord, la fonction qui recevra le paramètre doit être
définie de manière appropriée du fait qu'elle réceptionnera
un pointeur :
LOCAL FN afficheRectangle(adresseRectangle
AS PTR)
END FN
En second lieu, cela implique que vous ne travaillerez pas avec une copie
de la variable qui a été passée en paramètre
mais bien avec l'originale; toute modification des champs du record à
l'intérieur de la fonction locale affecte donc la variable d'origine.
Si pour une raison quelconque vous ne souhaitez pas altérer la
variable originale, il vous faudra faire une copie des valeurs qu'elle
contient dans une variable locale du même type. La copie de ces
données se réalise au moyen de l'instruction BLOCKMOVE
qui prend trois paramètres : l'adresse source où se
trouvent les données que l'on veut dupliquer, l'adresse de destination
où les octets copiés seront rangés et enfin le nombre
d'octets à copier. La commande BLOCKMOVE
est bien mal nommée, car elle pourrait laisser entendre que les
données sont déplacées de bloc en bloc, or elle en
fait bel et bien une copie en laissant les données d'origine en
place. Voici une façon possible de programmer ce mécanisme :
LOCAL FN afficheRectangle(adresseRectangle
AS PTR)
DIM rectangleLocal AS RECT
BLOCKMOVE adresseRectangle,@rectangleLocal,SIZEOF(rectangleLocal)
END FN
Il faut noter tout de suite que certains programmeurs affectionnent beaucoup
le raccourci syntaxique, optimisé pour des petites quantité
d'octets à copier, que propose FutureBASIC pour réaliser
la même opération :
LOCAL FN afficheRectangle(adresseRectangle
AS PTR)
DIM rectangleLocal AS RECT
rectangleLocal;8 = adresseRectangle
END FN
La valeur après le point virgule indique le nombre d'octets à
copier, cette valeur doit être une valeur littérale comme
dans l'exemple ci-dessus, ou bien une constante nommée; elle ne
peut pas être le résultat d'un calcul, ni d'une fonction
comme la fonction SIZEOF que nous avons utilisée
précédemment.
Il y a enfin, une façon plus simple d'arriver au même résultat,
et sans doute aura-t-elle votre préférence. FutureBASIC
permet d'affecter un type à un pointeur, si bien que vous pouvez
indiquer, dans la liste des arguments formels de la fonction, qu'un pointeur
d'un type donné sera réceptionné. Vous informerez
de cette façon le Compilateur sur la quantité d'octets qu'il
doit s'attendre à trouver à l'adresse qui aura été
transmise à la fonction, il pourra alors faciliter votre écriture de la façon
suivante :
LOCAL FN afficheRectangle(adresseRectangle
AS PTR TO RECT)
DIM rectangleLocal AS RECT
rectangleLocal = adresseRectangle
END FN
Dans l'exemple ci-dessus, FutureBASIC réalise automatiquement pour
vous la copie des octets en quantité appropriée. Cette facilité
d'écriture est bien pratique, mais elle pourrait être un
tant soit peu trompeuse, car elle n'est pas symétrique et vous
ne pourrez pas écrire ce qui suit, pour le cas où vous souhaiteriez
copier à nouveau les données de votre variable locale vers
l'adresse contenue dans la variable pointeur :
adresseRectangle = rectangleLocal
adresseRectangle est un pointeur, c'est-à-dire
qu'il contient une adresse mémoire occupant 4 octets (taille
d'un entier long) tandis que la variable rectangleLocal
est une structure (ici de 8 octets), vous devez donc dans ce cas
utiliser explicitement la commande BLOCKMOVE
pour copier les données d'un endroit à un autre de la mémoire :
BLOCKMOVE @rectangleLocal,adresseRectangle,SIZEOF(RECT)
Une difficulté supplémentaire peut survenir avec le passage
en paramètre d'un record à une fonction locale si vous souhaitez
utiliser la structure avec un appel de la Toolbox qui attend une variable.
Dans la fonction locale, vous ne disposez que de l'adresse de la structure
qui aura été réceptionnée dans un paramètre
entrant, ce paramètre est un pointeur qui sera placé en
registre. Par ailleurs, FutureBASIC contrôle les paramètres
passés aux appels de la Toolbox, et, de fait, lorsqu'une procédure
ou une fonction de la Toolbox attend une variable, FutureBASIC s'assure
de retrouver son adresse, car en effet, dans certains cas les appels de
la Toolbox doivent pouvoir modifier le contenu des variables et pour que
l'opération puisse avoir lieu il faut que ces variables soient
localisées en mémoire adressable. Ainsi si vous passez directement
le pointeur que vous avez reçu, le Compilateur se plaindra en vous
signalant qu'une variable en registre ne peut être utilisée
avec ce type d'instruction. Examinez, ce petit bout de code d'une fonction
qui afficherait un rectangle à l'aide de la procédure FrameRect
qui attend une variable de type RECT en paramètre :
LOCAL FN afficheRectangle(adresseRectangle
AS ^RECT)
FrameRect(adresseRectangle)
END FN
Ce code ne pourra pas être compilé, le Compilateur vous signalera
un peu maladroitement qu'il est impossible d'utiliser une variable en
registre avec cette instruction. Notez que si vous désactivez la
mise en registre des variables via les préférences, le code
pourra être compilé mais ne fonctionnera pas mieux. En réalité,
FutureBASIC essaie de résoudre l'adresse de la variable afin d'invoquer
la procédure de la Toolbox (FrameRect
accepte en paramètre une variable de type RECT),
or vous ne disposez pas d'une variable de ce type mais d'un pointeur sur
une variable de ce type, vous avez, en somme, déjà l'adresse
de la structure que FutureBASIC essaie de déterminer. Que faire
alors ?
Le problème n'est pas sans issue, et la première alternative
est de déclarer une variable locale de type RECT
et d'y copier les octets depuis l'adresse du pointeur entrant, comme dans
les exemples précédemment étudiés. Vous passez
ensuite la variable locale à la fonction de la Toolbox et le tour
est joué.
LOCAL FN afficheRectangle(adresseRectangle
AS ^RECT)
DIM rectangleLocal AS RECT
rectangleLocal = adresseRectangle
FrameRect(rectangleLocal)
END FN
Il y a cependant une possibilité plus directe en demandant à
FutureBASIC de ne pas contrôler le paramètre que vous envoyez
à la procédure de la Toolbox de la façon suivante :
LOCAL FN afficheRectangle(adresseRectangle
AS ^RECT)
FrameRect(#adresseRectangle)
END FN
L'utilisation du symbole # demande à
FutureBASIC de ne pas contrôler la validité du paramètre
qui est transmis et d'utiliser sa valeur en tant qu'adresse à envoyer
à la Toolbox.
Cette technique peut-être utilisée lorsque vous ne disposez
pas d'une variable à envoyer à un appel de la Toolbox qui
en attend une, cela peut arriver par exemple lorsque vous souhaitez envoyer
à la fonction une adresse en mémoire qui est le résultat
d'un calcul. Exemple :
FrameRect(#adresseConnue + 10)
Si dans un premier temps, vous butez trop souvent sur des problèmes
dûs à l'utilisation des registres, n'oubliez pas que vous
pouvez désactiver entièrement cette option dans les préférences
de l'Editeur à l'onglet variables (toutes vos variables seront
alors créées en mémoire adressable), mais je ne suis
pas enclin à vous encourager dans cette voie-là, essayez
plutôt de persévérer pour vous habituer très
tôt à tenir compte de ce facteur dans votre façon
de programmer.
Il y a encore deux choses à savoir concernant le typage des pointeurs
lorsqu'ils figurent dans la liste des arguments formels d'une fonction.
Tout d'abord, le Compilateur n'a aucun moyen de savoir si ce que vous
écrivez est correct ou pas concernant le type que vous attribuez
au pointeur. En réalité, vous pouvez considérer l'adresse
interceptée en paramètre comme n'importe quel type de pointeur.
C'est vous, en tant que programmeur, qui indiquez à FutureBASIC
la manière dont vous voulez "percevoir" les données
rangées à l'adresse en question. C'est en quelque sorte
un gabarit que vous appliquez arbitrairement à une zone en mémoire,
et dans certaines circonstances cela peut parfois s'avérer utile,
car vous aurez ainsi la possibilité de "découper"
les données suivant les champs d'un record qui n'est pas du type
original de la variable passée en paramètre. Par ailleurs,
si vous utilisez le typage de pointeur, vous pourrez vous dispenser des
identificateurs de type pour les champs du record, ce qui n'est pas le
cas avec un pointeur générique. Si vous utilisez un pointeur
générique (adresse AS PTR),
FutureBASIC n'a aucun moyen de savoir à quelle structure vous vous
référez, et vous devez utiliser des décalages au
moyen de constantes pour accéder aux champs du record. Dans ce
cas, il vous faut utiliser un identificateur de type pour indiquer au
Compilateur le nombre d'octets que vous souhaitez lire ou ranger à
l'emplacement mémoire concerné. Exemple :
LOCAL FN translaterRectangle(adresseRectangle
AS PTR)
adresseRectangle.left% +=
10
adresseRectangle.right% += 10
END FN
Avec un pointeur générique, ci-dessus, vous indiquez les
identificateurs de type pour les champs du record; les décalages
en mémoire sont calculés à partir de constantes _left
et _right prédéfinies par FB.
LOCAL FN translaterRectangle(adresseRectangle
AS ^RECT)
adresseRectangle.left +=
10
adresseRectangle.right += 10
END FN
En typant le pointeur, vous pouvez vous passer des identificateurs de
type pour les champs du record, car le Compilateur sait alors de quelle
structure vous voulez parler, et partant il peut déterminer l'espace
de stockage de chacun des champs de la structure.
Si, comme on le voit, il est possible de passer des records aux fonctions
(en fait leur adresse), il n'est en revanche pas possible de retourner
un record à une fonction appelante. Pour dire la vérité
cela semble "marcher" dans bien des cas, mais il n'est pas garanti
que cela "marche" à tous les coups. L'écriture
ci-dessous n'est pas recommandée et sera considérée
comme une erreur de programmation :
LOCAL FN creerRectangle
DIM rectangleLocal AS RECT
rectangleLocal.top
= 10
rectangleLocal.left
= 15
rectangleLocal.bottom = 20
rectangleLocal.right =
25
END FN = rectangleLocal
LOCAL FN SyntaxeNonRecommandee
DIM rectangle AS RECT
rectangle = FN creerRectangle
PRINT rectangle.top
PRINT rectangle.left
PRINT rectangle.bottom
PRINT rectangle.right
END FN
// Programme principal
FN SyntaxeNonRecommandee
En principe, les variables d'une fonction locale n'existent plus après
que la fonction a terminé son exécution (rectangleLocal
n'aura plus d'existence à la fin de la fonction creerRectangle,
souvenez vous que les variables disparaissent et que les blocs qui contiennent
leur valeur sont libérés), mais il se trouve que si vous
retournez une variable record, ce qui en principe n'est pas autorisé,
FutureBASIC renvoie son adresse (tout comme il sait déjà
réceptionner l'adresse d'une structure que vous passez en paramètre
à une fonction locale) et si vous utilisez une variable proprement
dimensionnée pour récupérer le résultat de
la fonction, il copiera gracieusement les octets qui sont rangés
à l'adresse retournée par la fonction. Vous n'avez pas de
garantie que cela fonctionnera à tous les coups.
On vient de le voir, le passage des paramètres à une fonction
est un aspect de la programmation qui n'est pas toujours facile à
réaliser, c'est pourtant un point essentiel auquel vous serez immédiatement
confronté dans la mesure où, comme il a déjà
été dit, les routines GOSUB/RETURN
ont depuis très longtemps été abandonnées
par les programmeurs qui utilisent FutureBASIC. En vérité,
si vous concevez correctement la nature d'une variable, les choses restent
cohérentes et finalement simples. Vous devez garder à l'esprit
la distinction entre la variable elle-même et son contenu :
Une variable a un nom symbolique (ex : maVariable)
auquel vous vous référez pour la manipuler dans vos instructions.
Une variable a un type (ex : INT) qui
indique sa dimension, c'est-à-dire la quantité d'espace
de stockage (en octets) dont elle a besoin pour stocker les valeurs qu'elle
est amenée à contenir.
Une variable a une adresse (@maVariable)
on parle souvent aussi de son pointeur, qui est un nombre (sur un entier
long) indiquant l'emplacement physique en mémoire qui contiendra
sa valeur. On a vu aussi, qu'avec le PowerPC, le contenu de certaines
variables pouvait être stocké dans des cases spéciales
ou registres et que de fait, ces variables n'avaient pas d'adresse en
mémoire.
Une variable a une valeur qui est composée d'un certain nombre
d'octets rangés à l'adresse physique de la variable.
Les choses ont tendance à se compliquer dès que l'on parle
de pointeurs et de handles.
Un pointeur est une variable dont le contenu est une adresse en mémoire.
(ex : unPointeur = @maVariable). Les pointeurs
ont la taille d'un entier long (4 octets). Les blocs qu'ils référencent
sont inamovibles.
Un handle est une variable pointeur dont le contenu est l'adresse d'un
autre pointeur qui indique à son tour l'adresse d'un bloc en mémoire.
Au gré de ses besoins, le Memory Manager du Macintosh (le
Gestionnaire de mémoire), peut avoir à déplacer le
bloc à une autre adresse en mémoire, et si cela doit arriver,
il se charge aussi de mettre à jour le handle, pour que l'adresse
indiquée par le handle pointe bien sur le nouvel emplacement du
bloc.
En dépit de son apparente complexité, l'utilisation des
handles est quasiment indispensable pour les projets conséquents.
FutureBASIC propose, pour faciliter la vie aux débutants, les variables
du type CONTAINER. Fort heureusement, et
je vous rassure tout de suite les containers sont plus difficiles à
expliquer qu'à utiliser. Un container n'est autre qu'un pointeur
dont le contenu est un handle, c'est à dire un autre pointeur qui
référence un autre pointeur encore. Cette triple indirection
peut donner le tournis, mais en réalité la manipulation
des containers est presque d'une simplicité enfantine pour le programmeur
tout en mettant à sa disposition les avantages procurés
par les handles.
On a déjà évoqué succinctement une limitation
des containers (dont les tableaux dynamiques souffrent aussi), c'est ici
l'occasion d'en reparler : cette limitation tient au fait que, de par
leur nature, ces types de variables ont une portée globale; et
en conséquence elles doivent être déclarées
dans un bloc structurel BEGIN GLOBALS/END GLOBALS.
Cela implique surtout que l'on ne peut déclarer de containers (ni
de tableaux dynamiques) à l'intérieur d'une fonction locale,
on ne peut pas plus retourner ce type de variable à une fonction
appelante.
On peut bien-sûr travailler avec des containers dans des fonctions
locales puisqu'elles "voient" les variables globales du programme
à condition qu'elles ne soient pas en LOCAL
MODE :
BEGIN GLOBALS
DIM gC AS CONTAINER
END GLOBALS
LOCAL FN remplirContainer
gC = "Chaîne rangée
dans le container"
END FN
// Programme principal
FN remplirContainer
PRINT gC
Jusqu'ici, il n'y a rien de spécial à signaler, mais maintenant
que nous connaissons la nature intime des containers, nous pouvons éventuellement
écrire une fonction en LOCAL MODE,
totalement indépendante du reste du programme et de ses variables
globales. Bien entendu, cela demande beaucoup plus de travail de notre
part, cela demande aussi de connaître la Toolbox du Macintosh, mais
cela reste néanmoins possible. Voici une nouvelle version pour
l'exemple précédent :
BEGIN GLOBALS
DIM gC AS CONTAINER
END GLOBALS
LOCAL MODE
LOCAL FN remplirContainer(@CPtr AS PTR)
DIM chaine AS STR255
chaine = "chaîne rangée
dans le container"
IF CPtr.nil& = _nil
THEN CPtr.nil& = FN NewHandle(0)
LONG IF CPtr.nil&
LONG IF FN PtrToXHand(@chaine[1],CPtr.nil&,chaine[0])
<> _noErr
DisposeHandle(CPtr.nil&)
: CPtr.nil& = _nil
END IF
END IF
END FN
// Programme principal
FN remplirContainer(gC)
PRINT gC
Ci-dessus, la fonction remplirContainer intercepte
l'adresse de la variable container qui lui est passée en paramètre.
Nous savons qu'à cette adresse est stocké un handle, nous
pouvons donc l'extraire pour l'utiliser avec les fonctions de la Toolbox
qui manipulent des handles. Pour connaître la valeur du handle,
il nous faut lire un entier long à l'adresse du pointeur, la syntaxe
"pointeur+point+décalage+identificateur de type" est
utilisée ici, nous aurions pu tout aussi bien écrire PEEK
LONG (CPtr) ou encore [CPtr] qui permettent
aussi de lire un entier long à une adresse en mémoire. Nous
commençons par tester si un handle est déjà présent
à cette adresse, si nous trouvons une valeur égale à
0, cela signifie que notre variable container n'a pas encore été
initialisée (FutureBASIC se charge d'initialiser pour nous les
containers quand il le faut). Si nous ne trouvons pas de handle, nous
en créons un à l'aide de la fonction de la Toolbox NewHandle
tout comme le ferait FutureBASIC à notre place. Remarquez que l'on
peut demander un nouvel handle pointant sur un bloc d'une taille de 0 octet
! On peut en effet, très facilement agrandir la taille d'un bloc
relogeable par la suite si cela est nécessaire. C'est d'ailleurs
là un des grands atouts des handles.
La fonction NewHandle retourne le handle
nouvellement créé ou bien 0 (on utilise souvent la constante
_nil à la place de la valeur 0) si
le Memory Manager n'a pu réaliser l'opération. La
valeur retournée par la fonction de la Toolbox est stockée
directement à l'adresse du pointeur.
Après quoi, nous testons si la valeur du handle n'est pas nulle.
Il faut impérativement s'assurer que les handles sont valides avant
de les utiliser dans une quelconque instruction, sinon gare à la
casse.
Si nous disposons effectivement d'un handle, nous essayons alors de copier
le contenu d'une chaîne de caractère dans le bloc référencé
par le handle. Nous utilisons ici la fonction de la Toolbox PtrToXHand
qui transfère les octets depuis une adresse en mémoire vers
un handle en remplaçant ce qu'il y avait déjà dans
le bloc de mémoire relogeable. La fonction accepte trois paramètres,
l'adresse mémoire où commencent les octets à copier,
le handle qui pointe sur le bloc relogeable où les octets seront
transférés et le nombre d'octets à copier, et elle
retourne un code d'erreur. Nous passons donc l'adresse du premier caractère
de la chaîne en omettant l'octet de longueur, car ici nous ne sommes
intéressés que par la suite de caractères, nous passons
le handle que nous avons récemment stocké à l'adresse
du pointeur et enfin le nombre de caractères de la chaîne
à copier.
Si la fonction échoue, elle retourne un code d'erreur, et dans
ce cas-là nous prenons des dispositions, à savoir ici, nous
libérons le bloc réservé pour le handle avec la procédure
de la Toolbox DisposeHandle et nous remettons
à 0 la valeur du handle stockée à l'adresse de notre
pointeur. Notez que la procédure DisposeHandle
ne remet pas à 0 la variable que vous lui passez en paramètre,
elle ne fait que libérer le bloc référencé
par la variable, c'est-à-dire qu'elle le rend disponible pour d'autres
usages éventuels. Si vous utilisez cette procédure, vous
devez de surcroît remettre votre variable à 0 pour plus de
sécurité. Sachez que FutureBASIC fournit une fonction préfabriquée
pour réaliser cette opération en une seule commande :
DEF DISPOSEH non seulement libèrera
le bloc et mettra à 0 la variable qui lui est passée en
paramètre mais aussi elle s'assurera que le handle qui lui a été
transmis n'est pas un handle sur une ressource, car les handles sur des
ressources doivent être traités différemment quant
à leur libération.
Bien évidemment, ce type d'exemple, n'est pas ce qu'on commence
par montrer aux débutants, mais son propos ici est de dévoiler
que l'on dispose d'une certaine liberté lorsqu'on comprend les
entités que nos instructions manipulent.
Le concept de fonction locale a été un apport essentiel
de FutureBASIC dans son implémentation du langage BASIC lui-même.
Les programmeurs qui connaissent déjà le langage BASIC standard
peuvent sans regrets abandonner immédiatement les routines GOSUB/RETURN,
ils n'y trouveront que des avantages à commencer par une meilleure
structuration de leur code source, mais aussi une plus grande facilité
de réutilisation pour d'autres projets des routines qu'ils écrivent,
une plus grande efficacité dans l'utilisation des ressources de
la machine et en particulier de la mémoire, une aide précieuse
pour le débogage (correction des erreurs), le flux du programme
étant plus facile à tracer (on conseille généralement
d'écrire de petites routines utilisant le moins de variables possible,
lorsqu'une fonction devient trop grande il est préférable
de la découper en écrivant d'autres fonctions spécialisées
auxquelles la routine fera appel), une plus grande simplicité pour
d'éventuelles évolutions comme l'ajout de fonctionnalités,
une puissance accrue grâce au passage de paramètres, au retour
d'une valeur et à la récursivité possible. Voilà
encore un nouveau terme !
[Precédent] [Table
des Matières] [Suivant]
|
{Note} |