Ce didacticiel est destiné aux nouveaux utilisateurs de la Simple Virtual Machine.
Dans ce didacticiel, vous allez créer vos propres adresses en mémoire.
Le temps de lecture de ce didacticiel est estimé à 15 minutes si les fonctions et lectures/écritures en mémoire ont été abordées.
Pour commencer, créez le canevas de l'application dans le fichier exécutable memoire.svm en utilisant ce code :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
PROCESS "application"
CODE "main" INLINE
:debug BREAK
END
END
Lancez l'application en mode débugueur, puis ouvrez la fenêtre de mémoire :
Il est facile de constater que la mémoire est vide par défaut ! En effet, aucune adresse n'est définie.
Pour utiliser la mémoire, il faut donc créer des adresses mémoire.
Modifiez le code pour obtenir :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory INT, STR, BLN, PTR
END
END
Puis lancez l'application dans le débugueur, et ouvrez la fenêtre mémoire. Après l'exécution de l'instruction :memory
, la fenêtre doit contenir :
L'instruction vient de créer quatre adresses mémoire. Maintenant, il est possible d'utiliser ces adresses nouvellement définies pour écrire puis lire des données.
Détaillons le résultat cette instruction :
Les adresses créées portent un type. Celui-ci permettra de garantir la nature de la valeur enregistrée à une adresse donnée. Ici, le bloc comporte quatre types de base de la machine :
INT
: l'adresse correspondante peut contenir un nombre entier,STR
: l'adresse correspondante peut contenir une chaine de caractères (souvent utilisée comme zone de mémoire à taille variable),BLN
: l'adresse correspondante peut contenir un booléen,PTR
: l'adresse correspondante peut contenir un pointeur (une adresse mémoire peut donc contenir une valeur qui désigne une autre zone mémoire).Maintenant, modifiez le code pour obtenir :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory ((INT*3, STR)*2, BLN)*2, PTR*4
END
END
La description du bloc peut être améliorée en ajoutant des multiplicateurs, et des parenthèses peuvent être utilisées pour créer des groupes. Ces groupes peuvent également recevoir un multiplicateur.
Lancez l'application en mode débugueur, et constatez l'état de la mémoire après l'instruction d'allocation mémoire :
Il peut être complexe de deviner à quelle adresse l'instruction va créer le bloc mémoire. Aussi, la zone de mémoire créée est constituée d'adresses contigües et donc facile à désigner avec un pointeur. En réalité, l'instruction retourne bien un pointeur sur la nouvelle zone mémoire, et ce pointeur peut être écrit en mémoire.
Modifiez le code pour obtenir :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory PTR
:memory ((INT*3, STR)*2, BLN)*2, PTR*4 -> &0
END
END
Lancez de nouveau l'application en mode débugueur :
La première allocation a créé un premier bloc contenant seulement un pointeur. Comme la mémoire est vide, ce bloc est placé à l'adresse &0
.
Ensuite, la seconde allocation crée le même bloc que précédemment mais cette fois, le pointeur qui désigne la zone créée est placé à l'adresse &0
: les différentes adresses de cette nouvelle zone peuvent être accédées à travers l'expression (@&0/indice)
.
L'utilisation du pointeur retourné devient obligatoire lorsque la taille d'au moins un bloc ne peut pas être connue depuis le code :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
ARGUMENT INT size
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory PTR, PTR
:memory INT*@&size -> &1
:memory STR*2 -> &2
"text" -> (@&2/0)
END
MEMORY size
END
Lancez l'application ainsi transformée (avec ici 5 comme argument) dans le débugueur :
L'instruction d'allocation mémoire permet une dernière facilité pour localiser où se trouvent les adresses créées : elle peut définir des alias sur des portions du bloc.
Modifiez le code pour obtenir :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
ARGUMENT INT size
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory PTR/zone
:memory INT*@&size/array, (STR, BLN)/sub1, PTR, (STR, INT)*2/sub2 -> &zone
END
MEMORY size
END
Lancez l'application dans le débugueur (ici, avec l'argument 3) :
La première allocation a créé l'alias "zone", et la seconde les alias "array", "sub1" et "sub2". Notez qu'il y a une adresse sans alias dans le bloc de la seconde allocation.
Ce mécanisme est très utile pour regrouper en une seule allocation mémoire plusieurs groupes d'adresses ayant des fonctions différentes.
:memory description_de_bloc_memoire
.,
),(
et )
),*
et le nombre de répétitions)./
et l'alias).Allouer de la mémoire est assez naturel lors de l'écriture d'une application. L'opération inverse, appellée libération mémoire, l'est beaucoup moins. Pourtant, il est crucial pour une application de contrôler avec précision les ressources mémoire qu'elle utilise.
Modifiez le code de l'application pour obtenir :
#!/usr/bin/env svm
DESCRIPTION
Memory example
END
LOG
DEBUG "Memory" STYLE "default"
PLUGIN "svmcom.so"
ARGUMENT INT size
PROCESS "application"
CODE "main" INLINE
:debug BREAK
:memory (PTR, INT)/parameters
@&size -> (parameters/1)
:call function parameters
:com.message @(parameters/1)
:shutdown
:label function
:memory INT -> &P
@(P/1) -> &@&P
:shift @&@&P (P/1)
:shift @&@&P (P/1)
:return
END
MEMORY size
END
Lancez l'application en mode débugueur, et exécutez l'application pas à pas en observant à la fois le code et la mémoire :
L'instruction :return
nous intéresse particulièrement ici : en plus de sortir de la fonction, elle va supprimer toute la mémoire allouée dans la fonction et cela peut se constater aisément dans le débugueur.
La mémoire allouée en dehors de toute fonction sera quant à elle libérée automatiquement à la fin de l'exécution de l'application.
La libération de la mémoire est automatique :
:return
. La mémoire ainsi allouée est dite locale à la fonction.Notez également que les fonctions prennent une nouvelle responsabilité ici, et que le code exécuté dans une fonction peut accéder à plus de mémoire que son appellant : les fonctions font office de contexte pour l'exécution du code.
Le mécanisme présenté ci-dessus permet aux fonctions d'utiliser de la mémoire pour leur propre fonctionnement. Cependant, pour que la fonction puisse être appellée depuis n'importe quel endroit du code, il faut respecter quelques principes pour l'accès à la mémoire locale de la fonction :
:memory
.Lorsque le premier paramètre est réservé à la mémoire locale, l'accès à la mémoire devient systématique :
(P/numéro_de_paramètre)
en partant de 1 pour les numéros de paramètres,(@&P/numéro_de_variable)
en partant de 0 pour les numéros de variable locale,Le code utilisé pour illustrer la libération mémoire implémente ce mécanisme. Observez la construction de la fonction et son appel.
Ces recommandations peuvent être ignorées ou partiellement suivies selon l'utilisation voulue de la fonction.
Une fonction appellable depuis n'importe où doit :
P
au travers du paramètre réservé pour la mémoire locale.Le canevas de définition d'une fonction s'écrit donc :
:label nom_fonction
:memory mémoire_locale -> &P
corps_fonction
:return
Le canevas d'appel de fonction (l'alias peut être remplacé par un pointeur sans alias si nécessaire) s'écrit donc :
:memory (PTR, paramètres)/alias
[ , initialisation_paramètres ] -> alias
:call nom_fonction alias
Vous venez de voir les mécanismes de base de la gestion de la mémoire.
Le couple d'opérations allocation et libération a été conçu pour être le plus simple et le plus agréable possible : l'allocation ne demande qu'une description fonctionnelle de la mémoire requise et la libération est automatique, évitant le célèbre écueil des fuites mémoire.
Une gestion bien maîtrisée des ressources mémoires est un des piliers fondamentaux de la stabilité des applications qui doivent s'exécuter pendant longtemps (de l'ordre de l'année, par exemple), et une libération automatique et systématique de la mémoire est une aide précieuse pour atteindre une telle maîtrise.