Programmer en langage C
136
© Éditions Eyrolles
7.5 Les pointeurs génériques
En C, un pointeur correspond à la fois à une adresse en mémoire et à un type. Précisément, ce
typage des pointeurs peut s’avérer gênant dans certaines circonstances telles que celles où une
fonction doit manipuler les adresses d’objets de type non connu (ou, plutôt, susceptible de
varier d’un appel à un autre).
Dans certains cas, on pourra satisfaire un tel besoin en utilisant des pointeurs de type char *,
lesquels, au bout du compte, nous permettront d’accéder à n’importe quel octet de la mémoire.
Toutefois, cette façon de procéder implique obligatoirement l’emploi de conversions explicites.
En fait, la norme ANSI a introduit le type pointeur suivant (il n’existait pas dans la définition
initiale du langage C, effectuée par Kernighan et Ritchie) :
void *
Celui-ci désigne un pointeur sur un objet de type quelconque (on parle souvent de
« pointeur générique »). Il s’agit (exceptionnellement) d’un pointeur sans type.
Une variable de type void * ne peut pas intervenir dans des opérations arithmétiques ;
notamment, si p et q sont de type void *, on ne peut pas parler de p+i (i étant entier) ou de
p-q ; on ne peut pas davantage utiliser l’expression p++ ; ceci est justifié par le fait qu’on ne
connaît pas la taille des objets pointés. Pour des raisons similaires, il n’est pas possible
d’appliquer l’opérateur d’indirection * à un pointeur de type void *.
Les pointeurs génériques sont théoriquement compatibles avec tous les autres ; autrement dit,
les affectations type * -> void * sont légales (ce qui ne pose aucun problème) mais les
affectations void * -> type * (elles seront d’ailleurs illégales en C++) le sont également,
ce qui présente les risques évoqués précédemment à propos des contraintes d’alignement.
On notera bien que, lorsqu’il est nécessaire à une fonction de travailler sur les différents octets
d’un emplacement de type quelconque, le type void * ne convient pas pour décrire les différents
octets de cet emplacement et il faudra quand même recourir, à un moment ou à un autre, au type
char * (mais les conversions void * --> char * ne poseront jamais de problème de contraintes d’alignement). Ainsi, pour écrire une fonction qui « met à zéro » un emplacement de la
mémoire dont on lui fournit l’adresse et la taille (en octets), on pourrait songer à procéder ainsi :
void raz (void * adr, int n)
{
int i ;
for (i=0 ; i
// illégal
}
Manifestement, ceci est illégal et il faudra utiliser une variable de type char * pour décrire
notre zone :
void raz (void * adr, int n)
{
int i ;
Delannoy Livre.book Page 136 Mercredi, 6. mai 2009 4:26 16
136
© Éditions Eyrolles
7.5 Les pointeurs génériques
En C, un pointeur correspond à la fois à une adresse en mémoire et à un type. Précisément, ce
typage des pointeurs peut s’avérer gênant dans certaines circonstances telles que celles où une
fonction doit manipuler les adresses d’objets de type non connu (ou, plutôt, susceptible de
varier d’un appel à un autre).
Dans certains cas, on pourra satisfaire un tel besoin en utilisant des pointeurs de type char *,
lesquels, au bout du compte, nous permettront d’accéder à n’importe quel octet de la mémoire.
Toutefois, cette façon de procéder implique obligatoirement l’emploi de conversions explicites.
En fait, la norme ANSI a introduit le type pointeur suivant (il n’existait pas dans la définition
initiale du langage C, effectuée par Kernighan et Ritchie) :
void *
Celui-ci désigne un pointeur sur un objet de type quelconque (on parle souvent de
« pointeur générique »). Il s’agit (exceptionnellement) d’un pointeur sans type.
Une variable de type void * ne peut pas intervenir dans des opérations arithmétiques ;
notamment, si p et q sont de type void *, on ne peut pas parler de p+i (i étant entier) ou de
p-q ; on ne peut pas davantage utiliser l’expression p++ ; ceci est justifié par le fait qu’on ne
connaît pas la taille des objets pointés. Pour des raisons similaires, il n’est pas possible
d’appliquer l’opérateur d’indirection * à un pointeur de type void *.
Les pointeurs génériques sont théoriquement compatibles avec tous les autres ; autrement dit,
les affectations type * -> void * sont légales (ce qui ne pose aucun problème) mais les
affectations void * -> type * (elles seront d’ailleurs illégales en C++) le sont également,
ce qui présente les risques évoqués précédemment à propos des contraintes d’alignement.
On notera bien que, lorsqu’il est nécessaire à une fonction de travailler sur les différents octets
d’un emplacement de type quelconque, le type void * ne convient pas pour décrire les différents
octets de cet emplacement et il faudra quand même recourir, à un moment ou à un autre, au type
char * (mais les conversions void * --> char * ne poseront jamais de problème de contraintes d’alignement). Ainsi, pour écrire une fonction qui « met à zéro » un emplacement de la
mémoire dont on lui fournit l’adresse et la taille (en octets), on pourrait songer à procéder ainsi :
void raz (void * adr, int n)
{
int i ;
for (i=0 ; i
}
Manifestement, ceci est illégal et il faudra utiliser une variable de type char * pour décrire
notre zone :
void raz (void * adr, int n)
{
int i ;
Delannoy Livre.book Page 136 Mercredi, 6. mai 2009 4:26 16
