L'utilisation des pointeurs est très puissante dans certains langages. Cette puissance et surtout le fait que l'on touche directement à la mémoire sans aucun contrôle, complexifie le développement d'une application.
Si l'on ne fait pas attention et que l'on accède à une zone mémoire qui ne nous est pas allouée, le processeur via le système d'exploitation génèrera une erreur de segmentation qui provoquera une exception voire fera planter l'application. De plus, comme les allocations mémoire sont réalisées en partie par le développeur, il doit également se charger de la libération de la mémoire lorsqu'il n'en a plus besoin, au risque de voir une fuite mémoire apparaître.
Tous ces inconvénients obligent le développeur à prendre en charge des choses supplémentaires, compliquant ainsi l'application et pouvant ajouter des bugs.
Pour toutes ces raisons, les pointeurs sont regardés avec une certaine méfiance. 01 Informatique les a qualifié d'« aussi puissants qu'ils sont dangereux », en expliquant les atouts du langage D. En effet, au vu des avantages et inconvénients des pointeurs, ce langage a été conçu pour en autoriser l'usage aux programmeurs appréciant leur efficacité, tout en fournissant constamment des solutions alternatives à ceux qui s'en méfient.
Les pointeurs peuvent également contenir l'adresse d'une fonction. Cette dernière peut ainsi être passée en paramètre à une autre fonction et être appelée.
Voici un exemple en C, de la déclaration d'un pointeur qui pointe successivement vers les fonctions fonction_1() et fonction_2() :
Ce code va produire la sortie suivante : affiche fonction_1affiche fonction_2
procedure fonction_1; begin writeln('affiche fonction_1'); end; procedure fonction_2; begin writeln('affiche fonction_2'); end; var fonc : procedure; begin { bloc principal } @fonc := @fonction_1; fonc; @fonc := @fonction_2; fonc; end.
On appelle arithmétique des pointeurs la possibilité de faire des opérations arithmétiques (incrémentation, décrémentation, addition et soustraction) sur les pointeurs. Cela revient à effectuer un déplacement en mémoire.
La particularité de cette utilisation des pointeurs, est que l'on se déplace par saut de la taille mémoire du type de donnée pointé par le pointeur et non par octet (qui est la plus petit unité accessible).
char tab[] = {'t', 'a', 'b', '\0'}; char * p1 = &tab[0];
Ici le pointeur p1, contient l'adresse du premier élément du tableau tab (ce qui est équivalent à p1 = tab
). Si l'on déréférence p1, nous aurions donc le caractère 't'.
p1 = p1 + 1;
En faisant cela, p1 ne pointe plus sur le caractère 't', mais sur 'a'. Dans cet exemple, le déplacement en mémoire a été de un octet (car le type char
vaut toujours un octet). Mais si le tableau avait contenu des données de type long
, le déplacement aurait été de quatre octets. Le déplacement se fait donc par saut de la taille mémoire du type de donnée pointé par le pointeur.
Encore une fois, la syntaxe en Pascal est très proche.
const { les deux définitions sont équivalentes } tab: array[0..3] of char = ('T', 'A', 'B', #0); var p1: ^char; begin p1 := @tab[0]; { p1 pointe sur le 'T' } inc(p1); { p1 pointe sur le 'A' } end.
Une différence importante pourtant reste, c'est qu'on ne peut que incrémenter et décrémenter un pointeur typé. Pour faire une addition proprement dite, il faut convertir en entier d'abord :
begin p1 := @tab[0]; { p1 pointe sur le 'T' } p1 := ptr( integer(p1) + 2 ); { p1 pointe sur le 'B' } end.
Dans ce cas-là, il faut prendre soi-même en charge la multiplication éventuelle par la taille du type des données pointées.