Démystifier PEEK et POKE
GW-Basic, utilisé par PC-Basic
Programme avec Graphique et Texte
Pour faire simple, le mot-clé le plus célèbre du langage de programmation BASIC est POKE.
Alors que j'étais encore à l'école primaire et que j'avais reçu en cadeau un Tandy TRS-80 Color Computer 2 pour mon anniversaire, je me souviens très bien avoir feuilleté le manuel couleur Tandy Getting Started with Color RASIC, fourni avec la machine, et y avoir lu un avertissement : l'utilisation de POKE peut être dangereuse pour la santé de votre ordinateur.
Bien sûr, cette affirmation n'a fait qu'éveiller (et non piquer) ma curiosité, mais cette curiosité était tempérée par la peur.
PEEK et POKE vont généralement de pair, mais POKE est bel et bien dangereux : une seule erreur de POKE peut faire planter un ordinateur, ou pire, provoquer un « Killer POKE », causant des dommages matériels permanents.
Imaginez ! Une seule ligne de code erronée dans un programme BASIC peut littéralement entraîner des dommages informatiques catastrophiques.
C'est une perspective assez effrayante, qui souligne également la puissance de POKE. Il me faudrait de nombreuses années avant même d’essayer de POKE (ou PEEK) sur quoi que ce soit ; et, même à ce moment-là, j’étais incroyablement prudent, d’autant plus que je n’avais pratiquement aucune idée de ce que je faisais.
Avance rapide jusqu'à la version finale de GW-BASIC, v. 3.23.
POKE et PEEK font toujours partie intégrante du langage, et c'est une partie que j'ai volontairement omis d'aborder dans cette section. Pourquoi ?
POKE et PEEK me rappellent les codes de triche des jeux vidéo ; les joueurs avertis, maîtrisant les séquences arbitraires de touches et de boutons (par exemple, HAUT, HAUT, BAS, BAS, GAUCHE, DROITE, GAUCHE-DROITE, B, A, DÉMARRER, qui est le célèbre code de triche de Konami) bénéficient d'un avantage déloyal sur ceux qui sont contraints de se contenter de leur talent de joueur.
Les codes de triche sont les rites d'initiation d'une société secrète de tricheurs. Pour les plus jeunes, le Game Genie, un périphérique sur lequel on branchait ses cartouches de jeu (comme celles de la Nintendo NES, de la SNES ou de la Sega Genesis), a propulsé les codes de triche à un niveau supérieur, permettant aux utilisateurs de pirater temporairement les adresses mémoire des ROM. Franchement, je n’ai jamais approuvé Game Genie, mais j’avoue que je l’ai utilisé.
De même, je n'ai jamais vraiment été convaincu par PEEK et POKE. Les fonctionnalités avancées offertes par PEEK et POKE n'auraient-elles pas dû être judicieusement intégrées aux mots-clés du langage informatique ?
Voyez ce passage du manuel Tandy Getting Started with Color BASIC :
« L'ordinateur couleur possède de nombreuses fonctionnalités graphiques inaccessibles avec les instructions classiques de Color BASIC. Cependant, grâce aux fonctions mémoire spéciales PEEK et POKE, vous pouvez utiliser et expérimenter nombre de ces puissantes fonctionnalités. Cela demande un peu de travail supplémentaire, mais les résultats peuvent être impressionnants.»
Je me demande encore : pourquoi devrais-je accéder directement aux adresses mémoire lorsque je programme dans un langage de haut niveau, censé m'en protéger ? (Inutile de préciser que je n'ai jamais été un grand fan des pointeurs du C ; je préfère largement l'approche sans pointeurs de Java.)
Pire encore, les différentes versions de BASIC, y compris celles de GW-BASIC, possèdent leurs propres codes POKE. Des configurations matérielles différentes entraînent des appels à des cellules mémoire et des registres différents.
Un jeu de codes peut inverser les couleurs à l'écran, tandis qu'un autre inverse le texte. Mais comme dans toutes les sociétés secrètes, ces codes PEEK et POKE étaient difficiles à obtenir.
Rares étaient ceux qui étaient disposés à en divulguer les secrets, et pratiquement personne ne proposait de compilation imprimée des listes de codes et de leurs effets.
Ne cherchez pas beaucoup d'informations sur PEEK et POKE, hormis quelques définitions et syntaxes superficielles, dans le manuel GW-BASIC. Seul le livre « Programmation en GW-BASIC » de P. K. McBride propose une brève introduction à PEEK et POKE, dans le chapitre intitulé « Approfondir ».
Puis les programmeurs ont délaissé les anachronismes PEEK et POKE, laissant à ceux qui découvriraient plus tard des variantes de GW-BASIC le soin soit de (1) programmer sans eux, soit de (2) expérimenter et, après de nombreux essais et erreurs, de trouver quelques POKE utiles.
Le PEEKing et le POKEing présentent un certain manque d'élégance, c'est pourquoi j'ai depuis longtemps adhéré au premier camp – en renonçant à utiliser ces mots-clés – malgré le fait que POKE pouvait, utilisé correctement (ce qui n'était pas chose aisée), atténuer certains des problèmes de ralentissement affectant nombre de mes programmes.
Mais je reconnais que certains lecteurs pourraient s'intéresser à ces deux mots-clés, et particulièrement à savoir si la réputation dangereuse de POKE est justifiée ou non, simplement le fruit d'une campagne de bouche-à-oreille alarmiste de la fin des années 1970.
Essayons donc de dépasser le battage médiatique et les mythes, et explorons au moins les bases de GW-BASIC PEEK et POKE.
PEEK et POKE permettent tous deux d'opérer au niveau granulaire des bits et des octets. Rappelons qu'un octet contient huit bits ; un bit peut prendre deux valeurs : 0 et 1.
Ainsi, dans le système binaire, les valeurs possibles d'un octet vont de 00000000 à 11111111, ce qui correspond, en décimal, à 0 à 255.
Chaque octet possède sa propre adresse ; les adresses possibles vont de 0 à 65 535.
Outre le binaire et le décimal, l'hexadécimal (base 16 : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F) est très souvent utilisé en informatique pour sa commodité ; les nombres décimaux de 0 à 65 535 correspondent, en hexadécimal, à 0 à FFFF.
Étant donné que l'hexagone est en base 16, nous pouvons ramener FFFF à 65 535 de cette façon :
15*16³ + 15*16² + 15*161 + 15*160= 65 535
Mais l'inverse, du décimal à l'hexadécimal, est plus complexe, car il faut diviser par 16 à plusieurs reprises, en capturant les restes au passage, puis en les ordonnant dans l'ordre inverse.
Convertir 65 535 en hexadécimal donnerait quatre restes : 15, 15, 15 et 15, ce qui donne FFFF.
Le nombre 65 535, qui est 216 - 1, soit 64 ko, devrait vous sembler vaguement familier. Lorsque vous exécutez GW-BASIC, l'en-tête de démarrage vous informe du nombre d'octets de mémoire disponibles pour votre programme : 60 300, soit environ 60 ko.
On peut donc en déduire qu'un peu plus de 5 000 octets sont inaccessibles à votre programme. Cependant, dans Programmation en GW-BASIC, P. K. McBride met en garde le lecteur contre les erreurs de l'écran de démarrage de GW-BASIC :
Cela [le message « 60 k de mémoire libre »] ne signifie pas que le reste de vos 512 k ou 640 k a été utilisé par l'interpréteur GW-Basic. En réalité, GW-Basic a une longue histoire et a débuté à l'époque des mémoires 64 k. L'une des limitations notables du langage est qu'il n'est pas encore adapté aux machines de grande capacité.
McBride a écrit cela en 1989. Ce qui était vrai à l'époque et l'est encore plus aujourd'hui : l'interpréteur GW-BASIC n'utilise qu'une fraction de la mémoire disponible de votre ordinateur.
Assez de théorie. Il est temps de se plonger dans le vif du sujet et de se salir les mains.
En mode direct de GW-BASIC, saisissez :
DEF SEG
L'instruction DEF SEG définit l'adresse du segment actuel. Sans définir d'adresse spécifique (c'est-à-dire sans argument, comme indiqué ci-dessus), l'instruction utilise par défaut le segment de données (DS) de GW-BASIC, utilisé pour stocker toutes les informations nécessaires au bon fonctionnement de votre programme.
GW-BASIC peut également
effectuer un décalage vers un emplacement mémoire spécifié si un argument est
utilisé avec DEF SEG ; des informations détaillées sur la procédure sont
disponibles dans les pages suivantes.
Ensuite, réservons de la mémoire
pour une variable entière et attribuons-lui une valeur. Pour déclarer et
initialiser une variable entière, nous utiliserons le symbole % (pourcentage) :
D%=10
Mais où GW-BASIC a-t-il réservé la mémoire pour stocker l'entier 10 ?
L'instruction VARPTR (pointeur vers variable) révèle l'adresse mémoire.
? VARPTR(D%)
La sortie pourrait être 4650. Ou, si vous utilisez PC-BASIC, 4724.
Comme l'indique la documentation en ligne de PC-BASIC, « la mémoire n'est que partiellement émulée en PC-BASIC » ; il peut donc y avoir des différences.
Supposons que D% soit stocké à l'adresse 4650. Nous pouvons utiliser PEEK pour « observer » la valeur stockée à l'adresse 4724 :
? PEEK(4724)
La sortie devrait être 10, puisque le contenu de D% était (vraisemblablement) stocké à cette adresse. Mieux encore, ignorez l'adresse et laissez simplement VARPTR la rechercher :
? PEEK(VARPTR(D%))
POKE est complémentaire de PEEK.
Au lieu d'examiner une valeur particulière d'une adresse, POKE modifie la valeur stockée dans l'adresse sans référence à la variable associée. Le format est le suivant (ne saisissez rien sur la ligne suivante) :
POKE ADDRESS, BYTE VALUE
L'adresse doit être comprise entre 0 et 65 535 (décimal), tandis que la valeur de l'octet doit être comprise entre 0 et 255 (décimal).
Nous allons insérer une nouvelle valeur dans l'adresse associée à D% :
POKE VARPTR(D%),11
Pour voir si cela a fonctionné, tapez :
? D%
Vous devriez maintenant voir 11 affiché à l'écran.
À moins que vous n'utilisiez PC-BASIC, où, curieusement, 10 est toujours attribué à D%.
L'avantage souvent cité de POKE est sa rapidité, grâce à l'accès direct à la mémoire. Nous allons maintenant créer un programme qui illustre clairement cet avantage.
Tout d'abord, sachez que les
adresses affectées à la mémoire écran commencent à la valeur hexadécimale B800
(soit 47 104 en décimal) ; pour signaler à GW-BASIC que vous utilisez
l'hexadécimal, votre valeur hexadécimale doit être précédée de &H. Une
instruction DEG SEG avec un décalage &HB800 doit donc être codée au début de
votre programme.
L'écran de démarrage par défaut de GW-BASIC est l'écran
texte SCREEN 0 ; comme c'est l'écran le plus simple, c'est celui-ci que nous
utiliserons en premier.
Nous demanderons à l'utilisateur une position sur l'écran, de 0 (coin supérieur gauche) à… des milliers d'autres (en bas à droite de l'écran).
Ensuite, nous lui demanderons un code ASCII, de 0 à 255 (cette plage de nombres est-elle une coïncidence ? Pas vraiment.
La table ASCII a été conçue
avec cette restriction sur un seul octet à l'esprit), ainsi qu'une couleur pour
le texte. Une adresse mémoire paire, par exemple 100, peut contenir un caractère
ASCII ; la cellule mémoire impaire immédiatement adjacente – dans ce cas, 101 –
peut contenir la couleur de ce caractère.
Vous trouverez ci-dessous
POKETEST.BAS, qui vous permet d'expérimenter le POKEing de l'affichage visuel.
Il y a même un effet spécial POKE à la fin du programme, qui applique à tout le texte à l'écran une couleur définie par l'utilisateur, lettre par lettre. Mieux encore, le programme fonctionne aussi bien en GW-BASIC qu'en PC-BASIC.
POKETEST.BAS
10 DEF SEG=&HB800
20 SCREEN
9:SCREEN 0:CLS
25 PRINT"--- POKE SCREEN TEST ---"
30 PRINT"Location on
screen: even number (0 = top left)."
40 PRINT"Color of character at that
location: even number+1."
50 INPUT"Location" ;L%
60 INPUT"ASCII code for
that location (max = 255)";CODE%
70 INPUT"Color";COL%
80 PRINT"POKE";L%;",";CODE%;"
and POKE ";L%+1;",";C0L%
90 POKE L%,CODE%
100 POKE L%+1,COL%
110
PRINT"Press <ENTER> to turn ALL text on this screen the color ";COL%;"..."
120 LINE INPUT T$
130 FOR Y%=1 TO 5001 STEP 2 'only POKE odd addresses to
change the color of characters
140 POKE Y%,COL%
150 NEXT Y%
Expérimentez avec POKETEST.BAS
et vous vous demanderez peut-être : s'il n'y a que 16 couleurs (de 0 à 15), que
font les 240 valeurs possibles restantes ? Des blocs de texte ombrés, des
caractères clignotants et d'autres effets étranges font la différence. N'hésitez
pas à expérimenter.
Oh, et cette dernière partie de POKETEST.BAS :
changer la couleur de tout le texte à l'écran ?
Imaginez la difficulté de coder sans l'instruction POKE.
Il faudrait d'abord utiliser la fonction SCREEN (et non l'instruction SCREEN), qui renvoie le code ASCII du caractère à une ligne et une colonne désignées ;
ensuite, il faudrait « réécrire » ce caractère au même endroit, mais avec une couleur différente.
De plus, il faudrait également veiller à traiter chaque ligne et chaque colonne séquentiellement.
Essayez d'écrire un tel algorithme. Quelle que soit l'optimisation de votre code, il ne sera jamais plus rapide que de simplement POKEing toutes les adresses mémoire étranges.
POKE fonctionne donc bien en mode texte uniquement. Mais qu'en est-il d'un SCREEN où des graphismes plus complexes sont possibles ? POKE peut-il nous apporter quelque chose d'utile ?
Concentrons-nous sur les graphismes CGA : les SCREEN 1 et 2.
Ce sont des versions « moindres » (c'est-à-dire plus limitées) des SCREEN 7 et 9, qui sont EGA, mais vous aurez malgré tout une bonne idée de POKE en action. Tapez et exécutez le programme ci-dessous.
Lisez attentivement les commentaires des deux premières lignes.
POKEGRAP.BAS
0 'POKE TEST--IN GRAPHICS MODE,
SCREEN 1 OR 2
1 'PRESS + OR - TO CHANGE THE VALUE POKED OR ESC TO QUIT
2
CLS
3 INPUT"Screen 1 or 2";SCR
5 SCREEN SCR:KEY OFF:CLS:VALUE%=1
10
DEF SEG=&HB800
20 FOR T%=0 TO 16383
30 LOCATE 10,10:PRINT "POKE
";T%;",";VALUE%
33 IF INKEY$=CHR$(27) THEN END
35 POKE T%,VALUE%
36 IF
INKEY$="+" THEN VALUE%=VALUE%+1
37 IF INKEY$="-" THEN VALUE%=VALUE%-1
38
IF VALUE%<0 THEN VALUE%=255
39 IF VALUE%>255 THEN VALUE%=0
40 NEXT T%
POKEGRAP.BAS démontre que POKE fonctionne de la même manière dans SCREEN 1 ou 2 que dans SCREEN 0, sauf qu'au lieu d'afficher des caractères, différents motifs sont générés. Les visuels devraient vous sembler familiers : la genèse de ces motifs est similaire aux masques de pavage détaillés dans la section précédant (voir PATTERN.BAS Des motifs de toutes les couleurs).
Notez également que la boucle qui commence à la ligne 20 a une limite supérieure de 16 383. Ce nombre n'est pas arbitraire ; il s'agit de 16 ko, ce qui correspond à la quantité de mémoire de la page mémoire réservée au mode CGA. Le mode EGA réserve beaucoup plus de mémoire pour les graphiques.
Quelques couleurs unies en mode CGA sont disponibles pour PAINT, mais pas autant qu'en mode EGA. Néanmoins, exécutez le programme suivant pour PAINT l'écran CGA d'une couleur, puis jetez un ŒIL sur les valeurs stockées dans les cellules mémoire.
PEEKKPNT.BAS
10 SCREEN 1:CLS:KEY OFF
15
P%=0
16 PRINT"PEEK-PAINT"
17 INPUT"PAINT SCREEN WHAT COLOR";C
18 CLS
20 PAINT(100,100),C
30 I$=INKEY$
40 LOCATE 15,15:PRINT"PEEK(";P%;")=";PEEK(P%)
50 IF I$="+" THEN P%=P%+1
60 IF I$="-" THEN P%=P%-1
70 IF
I$=CHR$(27) THEN END
80 GOTO 30
Bien sûr, pouvoir utiliser PEEK dans une adresse pour voir si une couleur particulière est codée dans un octet offre une alternative à l'instruction POINT.
Continuez à bidouiller encore plus avec PEEK et POKE, en apprenant tout ce que vous pouvez. Nous avons largement dépassé le stade où vous pourriez réellement endommager votre ordinateur en utilisant POKE. Pourtant, je conserve ma peur d'enfant de POKE. Et je ne l'approuve toujours pas.