reseau social

Utiliser les pointeurs

Un article de ToutProgrammer.com.

Les pointeurs sont une composante essentielle du développement en langage C, c'est pour cette raison qu'il est important de bien en comprendre le fonctionnement.

[modifier] Introduction

Lorsque vous travaillez avec des types simples (int, long, byte, char, ...) vous pouvez utiliser ceux-ci directement et appeler des fonctions avec: c'est le mode "standard" que l'on appelle passage de paramètres par valeur. Voici un exemple que nous pourrions retrouver dans beaucoup de programmes en langage C:

  1. void maFonction(int varEntiere, long varLongue)
  2. {
  3. ...
  4. }

Mais le cas se complique lorsque vous devez travailler par exemple avec des structures (mot clé struct), des tableaux ou des chaînes de caractères. Comme ces types trop sont compliqués, structurellement parlant, pour le système il n'est pas possible pour lui de les manipuler facilement: le système ne connaît pas la longueur des éléments dans le tableau, dans la chaîne ou la structure et n'est pas capable de manipuler ces entités comme un tout.

Il peut arriver également que l'on souhaite voir le contenu de variables données en paramètre de fonctions modifié dans ces mêmes fonctions. Il est bien entendu possible de passer par la valeur de retour de la fonction mais si celle-ci doit retourner un état indiquant si tout c'est bien passé, cela devient compliqué à gérer.

C'est pour ces raisons que les pointeurs (et références) ont été créés.

Un pointeur est un type simple que le système est capable de manipuler. Il représente l'adresse mémoire où une donnée est stockée. Toute donnée en mémoire a sa propre adresse (même un pointeur). En partant de ce principe, on peut se dire qu'il est facile et plus rapide de donner l'adresse d'un tableau à une fonction que le tableau lui même. Ensuite vous avez tout loisir de modifier le contenu de ce tableau dans la fonction en manipulant le pointeur. Voici un exemple de déclaration de fonction utilisant un pointeur pour l'un de ses paramètres:

  1. void maFonctionAvecPointeurs(int varEntierClassique, int *pointeurSurVariablesEntieres)
  2. {
  3. ...
  4. }
  5.  
  6. void main(void)
  7. {
  8. int unEntier;
  9. int tableauDEntiers[10];
  10. maFonctionAvecPointeurs(unEntier, tableauDEntiers);
  11. }

Vous remarquerez les S sur le nom de certaines variables, ils ne sont pas anodins puisque le pointeur n'indique en rien le nombre d'éléments pointés: il peut s'agir d'un pointeur sur une variable unique ou d'un pointeur sur la première occurrence d'un tableau. C'est d'ailleurs le cas puisque nous passons à maFonctionAvecPointeurs un tableau de 10 éléments.

[modifier] Pointeur sur une variable

Comme indiqué dans la précédente partie, l'esperluette (signe &) permet de retrouver en langage C l'adresse d'une variable. Voici l'affectation de pointeurs à partir de l'adresse de variables:

  1. void maFonction(int *varEntiere, int *varLongue, int *varFlotante)
  2. {
  3. ...
  4. }
  5.  
  6. void main(void)
  7. {
  8. int varEntiere = 3;
  9. long varLongue = 100000;
  10. float varFlotante = 3.1415926;
  11.  
  12. maFonction(&varEntiere, &varLongue , &varFlotant);
  13. }

Comme nous le voyons, maFonction attendait 3 pointeurs (signalés par les astérisques). Nous lui avons donc passé 3 pointeurs: un pointeur sur un int, un pointeur sur un longet un pointeur sur un float.

Comme un pointeur est une variable comme une autre, il est possible d'avoir un pointeur sur un pointeur. A quoi peut servir une telle chose ? Un cas simple pourrait être qu'il faille mettre à jour un pointeur passé en paramètre d'une fonction. Voici un exemple:

  1. #include <stdlib.h>
  2.  
  3. void maFonction(int **varEntiere)
  4. {
  5. *varEntiere = (int *)malloc(1000);
  6. printf("Adresse d'allocation: %p\n", *varEntiere);
  7. }
  8.  
  9. void main(void)
  10. {
  11. int *varEntiere;
  12.  
  13. printf("Adresse du pointeur: %p\n", &varEntiere);
  14. maFonction(&varEntiere);
  15. printf("Valeur récupérée: %p\n", varEntiere);
  16. }

La double * dans l'interface de la fonction indique qu'il faut passer à cette fonction un pointeur de pointeur.

Vous obtiendrez alors quelque chose du genre:

Adresse du pointeur: 0xbffffd24
Adresse d'allocation: 0x8049588
Valeur récupérée: 0x8049588

Il est possible de faire des choses particulièrement compliquées avec les pointeurs puisqu'il est possible de faire des chainages de pointeurs illimités ce qui peut devenir particulièrement illisible surtout lorsque vous devez reprendre un vieux code pour retrouver un erreur ou faire une évolution, donc un seul mot: PRUDENCE. Nous avons vu le cas d'un pointeur de pointeur. Imaginez ce qu'il se passe avec un pointeur de pointeur de pointeur ...

[modifier] Conclusion

Comme nous l'avons vu, les pointeurs peuvent être particulièrement utiles en programmation C.

Dès que l'on souhaite manipuler de grands blocs de données, il est impératif d'utiliser les pointeurs car le système n'est pas en mesure de manipuler de lui même de telles quantités de données pour les passer à des fonctions (par exemple).

Les pointeurs permettent également de faire des tableaux dynamiques ou plutôt ce que l'on appelle des listes chainées. Les listes chainées sont des structures C qui pointent les unes sur les autres ce qui est très pratique lorsque l'on ne sait pas à l'avance combien d'éléments seront stockés par un programme (exemple de la lecture des lignes d'un fichier texte de taille quelconque).

Les pointeurs sont finalement très simples à comprendre et à utiliser à partir du moment ou les développeurs sont particulièrement rigoureux.