Qu'est-ce que Pointeur ?

Variable dont la valeur est l'adresse mémoire d'une variable, d'un élément de tableau, d'une fonction, etc. En C, on le déclare comme int *p ; &x donne l'adresse de x, et *p lit ou écrit la valeur pointée par p. Ajouter un entier à un pointeur le fait avancer par pas égaux à la taille du type pointé. Dans la plupart des expressions, le nom d'un tableau est converti en pointeur sur son premier élément, si bien que a[i] a le même sens que *(a + i). La valeur qui ne pointe nulle part s'appelle le pointeur NULL.

Nom japonais: ポインタ

« Pointeur » apparaît dans 25 questions d'entraînement réparties sur 8 thèmes, dont Bases de la programmation en C, Structures de données et algorithmes, ISA et assembleur.

Domaine: Bases de la programmation en C

Déclaration et opérations de base

int *p; déclare p comme un « pointeur sur int ». Après p = &x;, qui y place l'adresse de la variable x, *p = 5; écrit 5 dans x. Les arguments des fonctions sont passés par valeur : pour modifier dans une fonction une variable de l'appelant, on passe son adresse sous forme de pointeur. Pour accéder à un membre à travers un pointeur sur une struct, on écrit p->m au lieu de (*p).m.

Arithmétique des pointeurs et tableaux

Ajouter 1 à un pointeur sur int fait avancer l'adresse non pas d'un octet, mais de sizeof(int) octets (4 octets dans de nombreux environnements). Le nom d'un tableau est converti en pointeur sur son premier élément, sauf par exemple lorsqu'il est l'opérande de sizeof ou de &. Ainsi, quand on passe un tableau en argument à une fonction, seule l'adresse de son premier élément est réellement transmise, et sizeof dans la fonction ne permet pas de connaître la taille du tableau entier.

Usage en embarqué et pièges

Sur un microcontrôleur, on lit et écrit les registres des périphériques en convertissant une adresse entière en pointeur, comme (volatile uint32_t *)0x40020014. Lire ou écrire à travers un pointeur non initialisé, un pointeur NULL ou un pointeur pendant qui désigne une zone libérée est un comportement indéfini, et sans système d'exploitation, cela peut corrompre d'autres données sans que personne ne s'en aperçoive.

S'entraîner : questions sur Pointeur

Par rapport à un tableau, quelle description d'une liste chaînée (liste dont chaque élément contient un pointeur vers l'élément suivant) est correcte ?

  1. ASi l'élément situé juste avant le point d'insertion est connu, on peut insérer un nouvel élément en modifiant seulement des pointeurs, sans déplacer les autres éléments ; en revanche, lire le i-ème élément oblige à parcourir la liste depuis le début
  2. BLa position du i-ème élément se calcule, si bien que n'importe quel élément se lit en temps constant, O(1)
  3. CLes éléments sont rangés de façon contiguë en mémoire, si bien que le cache et le préchargement fonctionnent bien lors d'une lecture dans l'ordre
Afficher la réponse et l'explication

Réponse : A. Si l'élément situé juste avant le point d'insertion est connu, on peut insérer un nouvel élément en modifiant seulement des pointeurs, sans déplacer les autres éléments ; en revanche, lire le i-ème élément oblige à parcourir la liste depuis le début

Dans une liste chaînée, une fois la position connue, l'insertion et la suppression ne demandent que la réécriture des pointeurs voisins, en O(1). Lire le i-ème élément oblige en revanche à suivre i pointeurs depuis le début, en O(n). Un tableau calcule l'adresse du i-ème élément à partir de son adresse de départ et de la taille d'un élément, et lit donc n'importe quel élément en O(1), mais une insertion au milieu coûte O(n) pour décaler les éléments suivants. De plus, les éléments d'un tableau étant contigus, le cache et le préchargement fonctionnent bien, alors que les nœuds d'une liste chaînée ont tendance à être dispersés en mémoire, si bien que même un parcours en O(n) peut être beaucoup plus lent.

int fait 4 octets et un pointeur 8 octets. Si la valeur (l'adresse) d'un pointeur sur int p est 0x1000, quelle est la valeur de p + 3 ?

  1. A0x100C
  2. B0x1012
  3. C0x1018
Afficher la réponse et l'explication

Réponse : A. 0x100C

Ajouter un entier à un pointeur le fait avancer par pas égaux à la taille du type pointé. int fait 4 octets : p + 3 pointe donc 3 × 4 = 12 octets plus loin, soit sur 0x1000 + 0xC = 0x100C. p + 3 est l'adresse de l'élément de tableau p[3], et *(p + 3) a le même sens que p[3]. Un pointeur sur char avance d'un octet à la fois, et un pointeur sur double (8 octets) de 8 octets à la fois.

Questions fréquentes

Quelle est la taille d'un pointeur ?

8 octets dans de nombreux environnements 64 bits, et 4 octets sur les microcontrôleurs 32 bits. Les pointeurs sur des données ont en général la même taille, qu'ils pointent sur un char ou sur un double. Un pointeur n'a pas forcément la même taille qu'un int : pour ranger une adresse dans un entier, on utilise uintptr_t de stdint.h.

Quelle différence y a-t-il entre const char *p et char *const p ?

const char *p est un « pointeur sur un char const » : on ne peut pas modifier les caractères à travers p, mais on peut faire pointer p lui-même ailleurs. char *const p est un « pointeur const » : on ne peut pas modifier p, mais on peut modifier ce qu'il pointe. Règle de lecture : const à gauche de * fige ce qui est pointé, const à droite fige le pointeur lui-même.

« Pointeur » dans les explications

Questions mentionnant « Pointeur » (25)

  1. Par rapport à un tableau, quelle description d'une liste chaînée (liste dont chaque élément contient un pointeur vers l'élément suivant) est correcte ?
  2. int fait 4 octets et un pointeur 8 octets. Si la valeur (l'adresse) d'un pointeur sur int p est 0x1000, quelle est la valeur de p + 3 ?
  3. int fait 4 octets et un pointeur 8 octets. Quelle est la sortie obtenue en exécutant le programme ci-dessus ?
  4. Quelle affirmation sur le pointeur renvoyé par la fonction make ci-dessus est correcte ?
  5. En C, quelle description de ce qui se passe lorsqu'un pointeur NULL est déréférencé (lecture de *p alors que p vaut NULL) est correcte ?
  6. Dans la convention d'appel standard de RISC-V, lors de l'appel d'une fonction à 10 arguments entiers, où sont transmis les 9e et 10e arguments ?
  7. Quelle description de l'ABI (interface binaire d'application) est correcte ?
  8. En RISC-V, quelle instruction réalise généralement un appel par pointeur de fonction en C, « (*fp)(x) » ?
  9. Dans la fonction ci-dessus, pourquoi le compilateur ne peut-il pas, en l'état, sortir de la boucle la lecture de *b ?
  10. Quel type d'instructions la vectorisation automatique du compilateur utilise-t-elle ?
  11. Que se passe-t-il en général lorsqu’un programme accède à une adresse que l’OS ne lui a pas allouée ?
  12. Quelle description du problème ABA, qui affecte les structures de données sans verrou utilisant CAS, est correcte ?
  13. Quelle est la définition de la mémoire unifiée (unified memory) de CUDA ?
  14. Un tas binaire est stocké dans un tableau a[0], a[1], a[2], … avec la racine en a[0], en remplissant chaque niveau de gauche à droite à partir du haut. Quels sont les enfants et le parent de a[4] ?
  15. Pour une table de hachage contenant de nombreux éléments et maintenue à un facteur de charge d'environ 0,5, quelle est la raison la plus appropriée pour laquelle l'adressage ouvert avec sondage linéaire tend à être plus rapide que le chaînage (relier les éléments par des listes chaînées) ?

Termes associés

Tableau Variable Comportement indéfini (undefined behavior) Liste chaînée (linked list) Argument Défaut de page (page fault) Microcontrôleur Préchargement (prefetch) Pile (stack) Allocation dynamique de mémoire

Retour au glossaire