Introduction

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.

Mise en place

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

Allocation mémoire

Etat initial de la mémoire

Lancez l'application en mode débugueur, puis ouvrez la fenêtre de mémoire :

Memory
Memory - K main - P application
AddressTypeValue
AliasPointer
Address:
Display
Aliases
P
Focus
Back
Clear

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.

Bloc 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 :

Memory
Memory - K main - P application
AddressTypeValue
&0INT
&1STR
&2BLN
&3PTR
AliasPointer
Address:
Display
Aliases
P
Focus
Back
Clear

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 :

  1. l'instruction alloue toujours un bloc de mémoire : les quatre adresses sont contigües dans la mémoire,
  2. l'instruction utilise une description du bloc faite des types à créer : les types des adresses correspondent à ceux indiqués dans l'instruction,
  3. l'instruction détermine seule où allouer le bloc : il n'y a aucun moyen fiable d'assurer que les adresses sont allouées à partir d'une adresse donnée.

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 :

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 :

Memory
Memory - K main - P application
AddressTypeValue
&0INT
&1INT
&2INT
&3STR
&4INT
&5INT
&6INT
&7STR
&8BLN
&9INT
&10INT
&11INT
&12STR
&13INT
&14INT
&15INT
&16STR
&17BLN
&18PTR
&19PTR
&20PTR
&21PTR
AliasPointer
Address:
Display
Aliases
P
Focus
Back
Clear

Pointeur

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 :

Memory
Memory - K main - P application
AddressTypeValue
&0PTR&1*22
&1INT
&2INT
&3INT
&4STR
&5INT
&6INT
&7INT
&8STR
&9BLN
&10INT
&11INT
&12INT
&13STR
&14INT
&15INT
&16INT
&17STR
&18BLN
&19PTR
&20PTR
&21PTR
&22PTR
AliasPointer
Address:
Display
Aliases
P
Focus
Back
Clear

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 :

Memory
Memory - K main - P application
AddressTypeValue
&0INT5
&1PTR&3*5
&2PTR&8*2
&3INT
&4INT
&5INT
&6INT
&7INT
&8STR"text"
&9STR
AliasPointer
size&0*1
Address:
Display
Aliases
P
Focus
Back
Clear

Alias

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) :

Memory
Memory - K main - P application
AddressTypeValue
&0INT3
&1PTR&2*10
&2INT
&3INT
&4INT
&5STR
&6BLN
&7PTR
&8STR
&9INT
&10STR
&11INT
AliasPointer
size&0*1
zone&1*1
array&2*3
sub1&5*2
sub2&8*4
Address:
Display
Aliases
P
Focus
Back
Clear

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.

Libération mémoire

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 :

Memory
Memory - K main - P application
AddressTypeValue
&0INT5
&1PTR&3*1
&2INT15
&3INT5
AliasPointer
size&0*1
parameters&1*2
Address:
Display
Aliases
P
Focus
Back
Clear
Code main - K main - P application
: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
Auto-scroll to
Current
with above
Display

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 :

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.

Fonctions utilisant de la mémoire

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 :

Lorsque le premier paramètre est réservé à la mémoire locale, l'accès à la mémoire devient systématique :

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 :

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

Conclusion

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.