PureBasic:Pointeurs
Un article de Games Creators Network.
Sommaire |
[modifier] Introduction
Vous venez de lire la doc sur les pointeurs et accès mémoire et ce n'est toujours pas clair ? espérons que cet article vous aidera un peu plus .
[modifier] Adresse
Il y a deux possibilités pour accéder à une variable
- par son nom, l'adressage immédiat
- par son adresse, l'adressage indirect
L’utilisation des pointeurs est liée à la mémoire puisqu'un pointeur est une variable spéciale qui contient toujours une adresse mémoire.
Toutes les variables d'un programme sont contenues en mémoire dans des cases que l'on nomme adresses. A une adresse correspond un octet, une variable occupera un ou plusieurs octets suivant son type. Il faut bien différencier le contenu d'une variable avec son adresse.
Exemple:
nombre.w=256 nombre ici est de type word, il est donc défini sur deux octets mais son adresse mémoire est définie sur 4 octets (système window 32bits).
Cela signifie que la variable nombre occupe deux adresses mémoires contenant chacune 1 octet. Une variable peut donc occuper plusieurs adresses mémoires d'octets contigus.
Connaissant le type d'une variable, seul nous est nécessaire de connaitre la première adresse d'une variable contenant l'octet de poids faible. la représentation de 256 sur deux octets donne 0 pour le premier et 1 pour le deuxième.
L' adresse mémoire d'une variable ne change pas au cours d'un programme, elle reste la même pendant toute la durée de vie de la variable; c'est Windows qui la détermine à chaque lancement du programme.
[modifier] Les variables numériques
Soit une variable: nombre.w
Pour connaitre son adresse mémoire (adresse mémoire de l'octet de poids faible) je place un @ devant le nom de la variable:
@nombre
Ensuite à partir de son adresse, je peux lire ou écrire dans cette variable de deux manières.
La première sont les instructions Peek() et Poke() que l'on complète par une lettre suivant le type de la variable qui va déterminé le nombre d'octets à lire.
Comme nombre est déclaré de type word(.w), nous utiliserons donc, PeekW() et PokeW()
; Exemple pratique: nombre.w=256 adresse=@nombre debug peekW(@nombre)
Allons un peu plus loin. Le nombre.w est définit sur deux octets et à chaque octet correspond une adresse mémoire. Nous allons le vérifier. Pour lire chaque octet, nous utiliserons PeekB.
nombre.w=256 adresse1=@nombre adresse2=@nombre+1 debug peekb(adresse1) ; le premier octet de la premiere adresse ;memoire de la variable est 0 comme expliqué plus haut debug peekb(adresse2) ; le deuxième contient 1
De même pour écrire dans une variable avec un pointeur :
nombre.w=256 adresse=@nombre debug peekW(adresse) ; je change la valeur de la variable nombre pokeW(adresse,123) debug peekW(adresse) debug nombre
L'une des premières utilisations des pointeurs est son utilisation dans les procédures.
Un pointeur utilisé dans une procédure peut nous permettre de renvoyer plusieurs valeurs.
Nous connaissons déjà le passage de paramètres par valeur. Dans l'exemple qui suit, la procédure va copier les valeurs de nombre1 et nombre2 et les affecter à valeur1 et valeur2. La modification des variables valeur1 et valeur2 ne peut affecter en aucun cas les variables nombre1 et nombre2.
; Exemple de passage de paramètres par valeur Procedure.l addition(valeur1.l, valeur2.l) valeur=valeur1+valeur2 ProcedureReturn valeur EndProcedure nombre1.l=11 nombre2.l=22 Resultat=addition(nombre1, nombre2) Debug Resultat ; resultat renvoie la valeur 33
Soit le code ci-dessus, imaginons que je souhaite pour deux valeurs obtenir non seulemment l'addition de ces deux valeurs mais aussi leur multiplication sans passer par des variables globales; nous allons devoir utiliser les pointeurs comme ceci:
; Exemple de passage de paramètres par pointeur (ou adresse) Procedure.l test(*valeur1.l, *valeur2.l) addition=PeekL(*valeur1)+PeekL(*valeur2) multiplication=PeekL(*valeur1)*PeekL(*valeur2) PokeL(*valeur1,addition) PokeL(*valeur2,multiplication) ProcedureReturn 1 EndProcedure nombre1.l=11 nombre2.l=22 Resultat=test(@nombre1, @nombre2) Debug nombre1 ; résultat de l'addition Debug nombre2 ; résultat de la multiplication
Dans cet exemple, nous avons passé à la procédure non plus les variables elles-mêmes, mais leurs adresses respectives à la procédure. La procédure utilise les pointeurs représentés par *valeur1 et *valeur2 pour pointer vers ces adresses. Les variables nombre1 et nombre2 ont servis de variable d'entrée-sortie puisque dans un premier temps, elles ont fournis leurs valeurs à la procédure et dans un second temps, elles ont permis de récupérer les résultats de l'addition et de la multiplication. Contrairement au premier exemple, la modification du contenu des variables pointées par les pointeurs a modifié les variables nombre1 et nombre2.
[modifier] Les pointeurs et les variables
Exercice 1 :
; Un petit test pour se mettre en bouche ; Nous avons 3 pointeurs et une variable ; chaque pointeur contient l'adresse du pointeur suivant ; et le dernier pointeur contient l'adresse de la variable ; Comment faire pour lire la valeur de la variable ; à partir du premier pointeur, c'est à dire *pointeur1 ? Ma_variable=123 *pointeur1:*pointeur2:*pointeur3 *pointeur1=@*pointeur2 *pointeur2=@*pointeur3 *pointeur3=@Ma_variable
Solution 1 (utilisation des PEEK) :
Ma_variable=123 *pointeur1:*pointeur2:*pointeur3 *pointeur1=@*pointeur2 *pointeur2=@*pointeur3 *pointeur3=@Ma_variable Debug PeekL(PeekL(PeekL(*pointeur1)))
Solution 2 (Utilisation des pointeurs) :
Ma_variable=123 *pointeur1:*pointeur2:*pointeur3 *pointeur1=@*pointeur2 *pointeur2=@*pointeur3 *pointeur3=@Ma_variable Structure test Valeur.l EndStructure *pointeur.test=*pointeur1 Debug *pointeur\Valeur *pointeur.test=*pointeur\Valeur Debug *pointeur\Valeur *pointeur.test=*pointeur\Valeur Debug *pointeur.test\Valeur
Explications :
Un pointeur fait référence à une variable (ou autre). Il se sert de l'adresse de cette variable pour créer cette référence. Un pointeur ne contient pas de données, mais pointe vers la variable, via l'adresse qu'il contient, dont il peut manipuler la ou les données.
Je crée un nouveau pointeur de type long *Pointeur.test
*Pointeur.test=*pointeur1
Ici le Pointeur pointe vers la même référence que *pointeur1. Donc *Pointeur et *pointeur1 font référence à la même adresse qui est celle de pointeur2. Un pointeur ne peut et ne doit contenir qu'une adresse qui sert de référence.
Debug *Pointeur\valeur
Permet de lire le contenu de la variable pointée. Comme le contenu est égal à l’adresse de *pointeur2 (@*pointeur2), ici on affiche la valeur de l’adresse de *pointeur2.
Je me sers maintenant du contenu de *pointeur2 (qui est l’adresse de *pointeur3 : @*pointeur3), et je lis le contenu de *pointeur3 qui est égal à Ma_variable :
*pointeur.test=*npointeur\Valeur Debug *pointeur.test\Valeur
Exercice 2 :
; simple exercice avec les chaines chaine.s="Pure Casic is the Cest" ; Changer la lettre C par un B ; en utilisant aucune fonction STRING
Solution 1 :
chaine.s="Pure Casic is the Cest" ; Changer la lettre C par un B ; en utilisant aucune fonction STRING Structure Liste Valeur.b EndStructure *pointeur.Liste=@chaine *pointeur+5 ;position du "C" de Casis à partir du début de la chaine *pointeur\Valeur=66 ;valeur Ascii du B *pointeur+13 ;position du C de "Cest" à partir de la position précédente *pointeur\Valeur=66 ;valeur Ascii du B Debug chaine
Solution 2 (sans structure - positionnement relatif) :
chaine.s="Pure Casic is the Cest" ; Changer la lettre C par un B ; en utilisant aucune fonction STRING *pointeur=@chaine *pointeur+5 PokeB(*pointeur,66) *pointeur+13 PokeB(*pointeur,66) Debug chaine
Solution 3 (sans structure - positionnement absolu) :
chaine.s="Pure Casic is the Cest" ; Changer la lettre C par un B ; en utilisant aucune fonction STRING *pointeur=@chaine PokeB(*pointeur+5,66) PokeB(*pointeur+18,66) Debug chaine
En traitant le problème d'une façon générale, ça donne ça:
*Pointeur.BYTE=@chaine ; .BYTE est une structure pré-déclarée dans PB
For a=1 To Len(chaine)
If *Pointeur\b=$43
*Pointeur\b=$42
EndIf
*Pointeur=*Pointeur+1
Next a
Debug chaine
ou ça :
*Pointeur=@chaine ; Un pointeur sur la chaîne
For a=1 To Len(chaine)
If PeekB(*Pointeur)=$43
PokeB(*Pointeur,$42)
EndIf
*Pointeur+1
Next a
Debug chaine
Exercice 3 :
; Autre exercice avec les chaines chaine.s="Pure Basic is the Best" chaine1.s=Space(Len(chaine)) ; Il faut que: chaine1 = "tseB eht si cisaB eruP" ; soit la chaine inversée de la première chaine ; traité le problème d'une façon générale ; aucune fonction STRING ne devra être utilisée ; à part la fonction len()
Solution 1 :
chaine.s="Pure Basic is the Best" chaine1.s=Space(Len(chaine)) Structure Liste Valeur.b EndStructure *pointeur1.Liste=@chaine *pointeur2.Liste=@chaine1 *pointeur1+Len(chaine)-1 For i=1 To Len(chaine) *pointeur2\Valeur=*pointeur1\Valeur *pointeur2+1 *pointeur1-1 Next Debug chaine1
Solution 2 :
chaine.s="Pure Basic is the Best" chaine1.s=Space(Len(chaine)) *pchaine = @chaine *pchaine1 = @chaine1 taille.l = Len(chaine)-1 For i.l = 0 To taille PokeB(*pchaine1 + taille - i , PeekB(*pchaine + i)) Next Debug chaine Debug chaine1
[modifier] Les pointeurs et les tableaux
Les exemples exposés ci-dessus sur les variables, sont applicables aussi aux tableaux à (n) dimensions.
Exercice :
Créer une procedure affichant les valeur d'un tableau à 2 dimensions, en passant les pointeurs en paramètres :
#dimx=5
#dimy=5
Dim tab.l(#dimx,#dimy) ;tableau de type .long
For i=0 To 5
For j=0 To 5
tab(i,j)=i+j+1 ;affectation de valeurs quelconques au tableau
Debug tab(i,j)
Next
Next
Solution :
#dimx=5
#dimy=5
Dim tab.l(#dimx,#dimy) ;tableau de type .long
For i=0 To 5
For j=0 To 5
tab(i,j)=i+j+1 ;affectation de valeurs quelconques au tableau
Debug tab(i,j)
Next
Next
Structure Liste
val.l
EndStructure
Procedure affich(*pointeur.Liste,nb)
For i=1 To nb
Debug *pointeur\val
*pointeur+4 ;saut de 4 octets (valeur d'un .LONG)
Next i
EndProcedure
Debug "------------------------------"
affich(@tab(),(#dimx+1)*(#dimy+1)) ;@tab() donne l'adresse du premier élément du tableau (tab(0,0))
[modifier] Les pointeurs et les liste chainées
Ceci n'est pas un tutorial, mais plutôt un éclaircissement sur la structure en mémoire d'une liste chainée. Lorsque l'on définit une liste chainées comme ceci :
structure Element valeur.l endstructure NewList test.Element()
Pure Basic modifie la structure en interne comme ceci:
structure Element *Next.Element *Previous.Element valeur.l endstructure Petite démonstration : <pre> Structure Liste Valeur.l EndStructure NewList Test.Liste() AddElement(Test()) Test()\Valeur=12 AddElement(Test()) Test()\Valeur=34 AddElement(Test()) Test()\Valeur=56 AddElement(Test()) Test()\Valeur=78 FirstElement(Test()) Debug Str(@Test()\Valeur) +"=adresse du 1er élément" NextElement(Test()) Debug Str(@Test()\Valeur) +"=adresse du 2ème élément" NextElement(Test()) Debug Str(@Test()\Valeur) +"=adresse du 3ème élément" NextElement(Test()) Debug Str(@Test()\Valeur) +"=adresse du 4ème élément" Debug "----------------------------" FirstElement(Test()) adresse=@Test()\Valeur Debug Str(PeekL(adresse-4)) +"=adresse élément precedent" Debug Str(PeekL(adresse-8)) +"=adresse élément suivant" Debug Test()\Valeur Debug "----------------------------" NextElement(Test()) adresse=@Test()\Valeur Debug Str(PeekL(adresse-4)) +"=adresse élément precedent" Debug Str(PeekL(adresse-8)) +"=adresse élément suivant" Debug Test()\Valeur Debug "----------------------------" NextElement(Test()) adresse=@Test()\Valeur Debug Str(PeekL(adresse-4)) +"=adresse élément precedent" Debug Str(PeekL(adresse-8)) +"=adresse élément suivant" Debug Test()\Valeur Debug "----------------------------" NextElement(Test()) adresse=@Test()\Valeur Debug Str(PeekL(adresse-4)) +"=adresse élément precedent" Debug Str(PeekL(adresse-8)) +"=adresse élément suivant" Debug Test()\Valeur
Graphiquement nous pourrions la représenter comme ceci :
Voilà, j'espère que ça va éclaircir pas mal de lanternes... Enfin, moi ça m'a permis d'y voir un peu plus clair...



