
FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 28, nombre de questions : 175, création le 11 janvier 2013
Sommaire→Pointeurs, tableaux et chaînes de caractères→Les pointeurs- Qu'est-ce qu'un pointeur ?
- Quand utiliser les pointeurs ?
- Qu'est-ce que NULL ?
- Comment utiliser un pointeur sur une structure ?
- Quelle est la différence entre sizeof(struct data) et sizeof(struct data *) ?
- Comment déclarer un pointeur sur une fonction ?
- p et q pointent sur deux objets identiques mais (p == q) renvoie toujours faux ! Pourquoi ?
- Qu'est-ce que *p++ incrémente ?
- Quelle est la différence entre pointeurs constants et pointeurs sur constante ?
- Comment connaître le type d'une variable adressée par un pointeur void * ?
Chaque donnée en mémoire a une "adresse". Cette adresse permet au processeur de retrouver la donnée et de demander sa lecture ou son écriture. Un pointeur est une variable qui contient une adresse (ça peut être l'adresse d'une variable, celle d'une fonction ou encore celle d'un élément d'un tableau, etc.). Par abus de langage, on utilise aussi parfois le mot "pointeur" pour désigner une "adresse".
Il est difficile de fournir une liste exhaustive des cas où on doit on devrait utiliser les pointeurs mais voici quelques exemples :
- Chaque fois qu'on veut passer "une variable" à une fonction.
- Chaque fois qu'on veut passer un objet de taille trop importante à une fonction. En effet, il est moins coûteux (en termes temps d'exécution et de consommation de mémoire) de passer l'adresse de l'objet seulement que de passer une copie de l'objet lui-même (qui peut faire plusieurs octets contre 4 seulement pour une adresse sur un processeur Intel 32 bits par exemple) à la fonction.
- Chaque fois qu'on veut allouer dynamiquement de la mémoire.
- Chaque fois que le langage l'impose (par exemple, le nom d'un tableau, sauf lorsqu'il apparaît en argument de l'opérateur sizeof, est toujours converti par le compilateur en l'adresse de son premier élément !).
NULL est une macro définie dans <stddef.h> (qui est inclus par stdio.h, stdlib.h, etc.) et sert à représenter une adresse invalide.
NULL est par exemple utilisé dans les fonctions retournant un pointeur pour indiquer que la fonction a échoué. Affecter NULL à un pointeur
sert donc à indiquer que le pointeur est actuellement inutilisé (ou pointe "nulle-part"). La norme stipule que n'importe quelle expression entière vallant 0 ou une telle expression castée en void * représente une telle adresse.
Un pointeur sur une structure se déclare comme tout autre pointeur :
struct base {
int a;
double b;
};
struct base Elem;
struct base * p = &Elem;
Pour accéder au membre de la structure via le pointeur il est nécessaire de déréférencer ce pointeur :
(*p).a = 5;
Cette écriture pouvant se simplifier en :
p->a = 5;
(*p).a n'est cependant pas strictement équivalent à p->a car la première déréférence p (lit sizeof(struct base) octets à partir de cette adresse) avant d'entrer dans le champ a alors que p->a accède directement à la mémoire utile. (*p).a est donc non seulement plus long a écrire que p->a mais également moins performant.
sizeof(struct data) fournit la taille de la structure alors que sizeof(struct data *) fournit celle d'un pointeur.
Le plus simple est de définir dans un premier temps le type de la fonction sur laquelle on veut pointer :
typedef int F(void); /* F : type de la fonction f. */
/* Voici la fonction f en question : */
int f(void)
{
return 1;
}
Un pointeur sur f se déclare donc ainsi :
F * p = &f;
En fait, le nom d'une fonction (f), lorsqu'il n'est pas utilisé dans sizeof ou avec l'opérateur & ("adresse de"), est toujours converti par le compilateur en &f. Cela signifie qu'on aurait pu également écrire tout simplement :
F * p = f;
Et enfin, pour appeler f, les deux écritures suivantes sont toutes valides :
y = p();
y = (*p)();
Sans typedef, p devrait avoir été déclaré ainsi :
int (*p)(void) = f;
En effet, on veut un pointeur p vers une fonction qui ne prend aucun argument et qui retourne un int. (*p) vaut donc "une fonction" (même si une telle expression, sauf si elle est utilisée comme argument de sizeof, sera toujours automatiquement convertie en l'adresse de la fonction ...). Cette fonction prend void comme argument retourne un int. Autrement dit, (*p)(void) vaut un int d'où int (*p)(void) !
C'est tout à fait normal, le test (p == q) compare les deux pointeurs et non le contenu des emplacements qu'ils pointent. Pour comparer le contenu, il faut comparer *p et *q ou utiliser la fonction memcmp.
int * p, * q, n = 10;
/* On suppose ici que malloc ne peut pas echouer */
p = malloc(sizeof(int));
q = malloc(sizeof(int));
memcpy(p, amp;&n, sizeof(int)); /* Ou simplement *p = n; */
memcpy(q, amp;&n, sizeof(int)); /* Ou simplement *q = n; */
/* (*p == *q) mais (p != q) */
free(p);
p = q;
/* Maintenant (p == q) et donc naturellement (*p == *q) */
free(p); /* Ou free(q) */
Les opérateurs unaires (*, ++ et --) ont une priorité élevée et sont évalués de droite à gauche.
*p++; retourne la valeur de la zone mémoire désignée par p et incrémente ensuite p.
(*p)++; incrémente quant à lui la valeur de la zone mémoire désignée par *p.
Il convient de ne pas confondre les pointeurs constants (le pointeur ne pourra pas être modifié, mais la variable pointée si) et les pointeurs sur constantes (le pointeur pourra être modifié mais pas la variable pointée).
char buf[] = "bonjour";
char * const p = buf; /* Le pointeur p est declare constant */
p++; /* Incorrect car on ne peut pas modifier une constante */
p[4]++; /* Correct */
char buf[] = "bonjour";
char const * p = buf; /* Ou const char * p = buf; */
p++; /* Correct */
p[4]++; /* Incorrect car les donnees pointees par p ont ete declarees constantes (const char) */
Il est bien entendu possible de combiner les deux pour créer un pointeur constant sur des constantes :
char buf[] = "bonjour";
char const * const p = buf;
p++; /* Incorrect */
p[4]++; /* Incorrect */
La mémoire n'étant pas typée (le contenu de la mémoire dépend toujours de l'interprétation qu'on en fait), on ne peut pas connaître le type d'un objet pointé par un void *. Si le pointeur est utilisé pour créer une fonction générique, il est nécessaire de rajouter à la fonction un paramètre indiquant la taille ou le type de la variable utilisée :
typedef enum {
TYPES_char,
TYPES_short,
TYPES_int,
TYPES_long,
TYPES_float,
TYPES_double
} TypePtr;
void MyFunction(void * Ptr, TypePtr Type);



