< < E-NEF > >

Création de sites | Moniteurs | Chercher | Voyager | Cartes

()

Titre : Les types pointeurs.

Auteur : Emmanuel PIERRE

 

On parle souvent des pointeurs en programmation comme meilleure approche de la gestion de variables, puisque implicitement, c’est de cette façon que sont gérées les variables. Mais que sont les pointeurs ?

Comme son nom l’indique, un pointeur pointe (ou plutôt indique) l’emplacement d’un objet. Entendre par objet une suite d’un ou plusieurs éléments, comme des variables simples (Integer, byte, char...), ou des enregistrements (Struct ou Record, i.e. plusieurs variables regroupées à la même enseigne).

Pour bien comprendre les pointeurs, il faut savoir que l’adressage en mémoire d’un ordinateur est linéaire, c’est à dire que les cases mémoires se suivent, et que pour repérer une variable, il faut avoir son segment ( pour une mémoire séparée en plusieurs segments, ce qui facilite l’allocation de programmes de diverses tailles) et son déplacement sur ce segment ( offset ). L’unité de base d’allocation étant un BYTE, c’est à dire 8 bits (i.e.11111111 en binaire).

Pour un programmeur normal, utiliser les adresses directement est peu utile, et de toutes façons toute transparente. Déclarer un pointeur fait qu'il contient l’adresse renvoyée par le système.

Par exemple en PASCAL, je crée une variable de type pointeur, une variable normale, et je demande à ce que la première pointe sur la seconde, d’où deux façons d’accéder à la même case :

var p : ^string;

A : string;

begin

p := @A;

A := 10;

writeln(A);

p^:=p^+2;

writeln(p^);

writeln(A);

end.

Le sigle @ signifie en pascal " Adresse de ", donc par un petit schéma:

 

Dans P il y a une adresse, et donc pour accéder au contenu de l’adresse, il faut transformer cette adresse en variable, c’est ce que l’on fait avec le ‘^’ qui dit de prendre ce qu'il y a à cette adresse. Comme vu dans l’exemple, on peut passer une adresse directement à un pointeur p:=@A, ou bien passer dans la variable le contenu de la zone pointée A:=P^.

L’intérêt des pointeurs serait infime s’il n’y avait que ces possibilités d’usage. Le principal intérêt des pointeurs est qu’avec eux, on peut créer de nouvelles variables indépendantes à tout moment. Il est évident que lorsque l’on fait un traitement de texte, on ne va pas allouer des pages de 32ko en tant que variable, alors qu’à tout moment on peut les créer, les détruire et ainsi gagner de la mémoire. Il en va de même avec les listes chaînées, où l’ajout d’un membre nécessite de créer une nouvelle case.

Donc créer un pointeur d’élément nécessite de définir le type d’élément pour que le programme sache la taille à allouer au moment de la création de la variable. En clair, un pointeur sur un entier long est plus grand que sur un byte en espace mémoire, et plus petit qu’une page de 32ko. Un type POINTER pur ne représente que l’adresse, sans définir ce que l’emplacement contient, il ne contient pas cette information. Alors que ^string me dit qu’en pointant sur cette case, j’aurais une chaîne de caractères.

Par exemple:

program toto;

var

str : ^string;

byt : ^byte;

proc : procedure;

 

procedure Salut;far;

begin

writeln('Two roads...');

end;

 

begin

new(str);

str^:='Hello World';

writeln(str^);

dispose(str);

new(byt);

byt^:=25;

byt^:=byt^+10;

writeln(byt^);

dispose(byt);

proc:=Salut;

proc;

end.

Quand on définit le type ^string par exemple, on crée une variable qui pointe dans le vide ( de valeur NIL pour not in list ) ou non si elle n’est pas initialisée, ce qui signifie que telle qu’elle, elle est inutilisable voir même dangereuse si on l’utilise telle-quelle. Il faut alors lui réserver une place en mémoire, conformément à sa taille, ce que le programme fera automatiquement lors de l’appel de NEW(nom_de_variable_pointeur). A partir de ce moment, la variable existe, et peut être utilisée normalement, et lorsque l’on n’en aura plus besoin, il sera facile de la désallouer en donnant DISPOSE(nom_de_variable_pointeur).

Une variable pointeur désallouée peut être réallouée autant de fois que nécessaire, dans une suite de new-dispose.

Autre aspect des pointeurs discret, comme dans l’exemple ci-dessus, on voit que l’on a définit une variable de type procédure, et que l’on passe la procédure Salut dans celle proc par une simple affectation. Ceci est géré comme un échange d’adresse pour un type pointeur.

Un grand exemple d’utilisation de pointeurs et d’allocation dynamique, les listes chaînées, quand on rajoute un élément dans la liste, on crée une nouvelle case. Voici la définition d’une cellule :

Type

Liste = ^Cellule;

Cellule = Record

Caract : Char;

Suivant : Liste;

End;

On définit Liste en tant que pointeur sur un type cellule que l’on définit juste après. Remarquez la définition récurrente autorisée.

Une autre utilisation se fait à travers les enregistrements, on a un enregistrement de taille variable, on va avoir beaucoup d’enregistrements du même type, et on voudrait utiliser le moins d’espace mémoire possible. Pour cela on fait un regroupement dans un enregistrement:

type

Numtab = array[1..32000] of ^string;

 

BIGnum = record

t : numtab;

s : integer;

end;

Dans le champ BIGnum, on a utilisé un tableau de variables pointeur. On définit que dans T on peut aller jusqu’à mettre 32000 chaînes de caractères, mais on ne sait pas combien il y en aura effectivement, donc on attend, et au fur et à mesure des besoins, on rajoute les chaînes manquantes de cette façon:

var num : BIGnum;

i : integer;

str : string;

begin

for i:=1 to 32000 do

begin

readln(str);

if str=#13 then break

else

begin

new(num.t[i]);

num.t[i]^:=str;

end;

end;

num.s:=i;

end;

Donc on crée la variable-pointeur par un NEW, puis on utilise les membres de l’enregistrement normalement pour les remplir. Ici il s’agit d’un exemple un peu complexe déjà, mais qui préfigure bien des besoins en général que l’on peut avoir.

Pour désallouer, une simple procedure:

procedure desallocate(BN:BIGnum);

var i : integer;

begin

for i:=1 to BN.s do

begin

dispose(BN.t[i]);

BN.t:=nil;

end;

BN.s:=0;

end;

C’est en effet très simple. L’intérêt de cet exemple est de montrer que l’on gère jusqu’à 32000 chaînes de caractères sans problème d’allocation mémoire. En général, sous DOS, le segment de variables est de 64ko, ici avec l’allocation dynamique, on peut en obtenir beaucoup plus.

 

Conclusion

Pour utiliser les variables-pointeur, toujours penser à définir, allouer, libérer, et à mettre la variable libérée à NIL, ce qui évitera toute erreur de manipulation ou plantage par la suite.

Pour aller plus loin ...

Sous DOS, le programme Turbo-Pascal s’alloue toute la mémoire disponible, mais limite son segment de donnée à 64ko. Le reste est donc disponible pour l’allocation dynamique.

En C existe une fonction MALLOC qui permet d’allouer toute une plage mémoire pour des variables de type différent, alors qu’à moins d’un trans-typage, le Pascal permet difficilement cela.

Famous last words :

J’espère que ce petit topo vous servira dans la compréhension des pointeurs.

Remerciements

Vincent à qui j’ai essayé d’expliquer les pointeurs, ‘espère que cela lui apportera les compléments nécessaires,

Et aussi à tout ceux qui se trouvent limités par les barrières du DOS.

 

" The woods are lovely dark and deep

But I have promises to keep,

And miles to go before I sleep

And miles to go before I sleep. "

R.FROST