home *** CD-ROM | disk | FTP | other *** search
-
- ==========================================================================
- APPRENTISSAGE du C version ALB_10
-
- CHAPITRE 8: DES POINTEURS.
- ==========================================================================
-
-
- 1. LA RELATION DE BASE.
- ========================
-
- ************
- Nous avons déjà rencontré et utilisé des pointeurs. CH08_01.C
- Leur propriété fondamentale est appliquée dans ces deux CH08_02.C
- petits programmes. Tout le reste découle de ces propriétés. ************
-
- Avant d'analyser ces programmes, relisez attentivement
- le paragraphe 5.5. du chapitre 3 et accrochez vous!
-
- 1° Dans le premier programme, on déclare deux variables entières: une
- variable normale "a" et un pointeur "*pa".
-
- Souvenez vous:
-
- La déclaration de la variable ordinaire "a" entraîne la réservation
- d'une zone de mémoire dont la taille est fonction du type de "a".
- Le système crée une table qui conserve son nom et son adresse que
- nous pouvons connaître en utilisant l'opérateur de référencement "&a".
- C'est pourquoi cette adresse est aussi appelée la référence de la
- variable.
- Au moment de l'initialisation, a= 32, le système cherchera dans ses
- tables une variable du nom de "a" et placera à son adresse 32.
-
- La DECLARATION du pointeur "*pa" entraîne la réservation d'une zone
- de mémoire dont la taille est celle d'une adresse, 2 octets, la taille
- d'un entier non signé.
- La notation *pa nous indique que le pointeur est le résultat d'un
- déréférencement d'une variable pa.
- L'application de l'opérateur de référencement "&" au pointeur nous donne
- &(*pa)= pa. Nous appellerons donc "pa" la référence du pointeur.
- C'est une variable adresse, placée dans la zone de mémoire réservée au
- moment de la déclaration du pointeur.
- Cela ne présente en général que peu d'intérêt mais on pourrait connaître
- l'adresse de cette zone en faisant &pa.
-
- L'INITIALISATION du pointeur se fait en affectant à la référence "pa"
- l'adresse de la variable "a". Le programme affiche la valeur de cette
- adresse. L'adresse de la variable "a" a été fixée une fois pour toutes par
- le compilateur au moment de la déclaration de "a" et lui restera attachée
- jusqu'à sa destruction à la fin du programme. C'est une constante.
- Par contre l'adresse qui est le contenu de la référence "pa" pourra
- être modifiée. La référence est une variable.
-
- La variable et le pointeur ont maintenant la même référence.
- Puisque le programme utilise cette référence, cette adresse, pour
- écrire des valeurs dans la mémoire, il y aura une équivalence entre
- le pointeur et la variable. Vérifions:
- Chapitre 8: Des pointeurs page 1
-
-
-
- La variable "a " est initialisée à 32.
- On constate que c'est aussi la valeur de "*pa".
- Le pointeur indique la valeur de la variable qu'il pointe,
- variable dont l'adresse est la valeur de sa référence "pa".
-
- -------------------------------------
- | pa= &a => *(pa)= *(&a)= a |
- -------------------------------------
-
- Remarque 1: Une adresse a pour valeur un nombre, mais par sa nature ce
- n'est pas un nombre ordinaire.
-
- int a, b;
- b= &a; est incorrect et générera une erreur
-
- Ici, b n'est pas une adresse, c'est une variable ordinaire.
-
- Remarque 2: La déclaration et l'initialisation du pointeur se sont faites
- en deux temps:
-
- int a, *pa;
- pa= &a;
-
- Evidemment il faut d'abord déclarer la variable "a". Puis on affecte
- à la référence du pointeur, qui est une variable ADRESSE l'adresse "&a".
-
- Une écriture permet de faire ces deux opérations simultanément:
-
- int a, *pa= &a;
-
- Cette méthode est recommandée car il est dangereux de laisser se
- promener un pointeur sans affectation dans un programme.
-
- Remarque 3: On initialise un pointeur en lui affectant une adresse et
- non pas un nombre.
-
- int *pa= 16; est incorrect car à l'initialisation
- on doit lui affecter une adresse.
-
- Remarque 4: Dans certains cas, quand il n'est pas possible de déclarer
- d'abord la variable, il faut laisser le pointeur en attente d'affectation.
- C'est très dangereux car il pointe sur n'importe quoi. Si plus loin dans
- le code quelqu'un écrit un jour: *pa= x; la valeur de la variable x sera
- copiée à l'adresse mémoire pa, c'est à dire n'importe où.
- Pour éviter ce risque on affecte au pointeur la constante symbolique NULL
- dont la valeur est zéro et qui est définie dans stdio.h, string.h, mem.h,
- stdlib.h, alloc.h. Il faut réserver ce symbole aux pointeurs pour des
- raisons de clarté.
- int *pa= NULL;
-
- La plupart des compilateurs actuels considèrent cette adresse comme
- interdite et provoquent une erreur si on cherche à y écrire. Dans ces
- conditions un pointeur NULL ne pointe sur rien, alors qu'un pointeur
- non affecté pointe sur n'importe quoi.
-
- Ce petit programme donne à réfléchir mais n'a guère d'intérêt pratique.
- Nous avons modifié un pointeur à partir d'une variable mais nous aimerions
- plutôt faire le contraire, modifier une variable à partir d'un pointeur.
- Chapitre 8: Des pointeurs page 2
-
-
-
- 2° Nous déclarons la variable "a", sans l'initialiser, et le pointeur
- "*pa" que nous initialisons aussitôt avec l'adresse "&a".
-
- La valeur 16 est affectée au pointeur: *pa= 16; cette fois le pointeur
- possède une référence. On peut lui affecter un nombre.
-
- Nous constatons que la valeur de la variable "a" a été indirectement
- initialisée. De là viens le terme anglais "indirection" que nous
- traduisons par déréférencement. Chez nous les latins, c'est la définition
- qui sert à nommer l'opération, alors que chez les anglo-saxons c'est son
- utilité!
- --------------------------
- | pa= &a; => a= *pa |
- --------------------------
-
- ************
- Ce petit programme peut être une source de méditation! CH08_03.C
- Mais surtout pas un modèle de programmation! ************
- Cette fois ci, variables et pointeurs sont de type double.
-
-
-
-
- 2. PRINCIPALES PROPRIETES DES POINTEURS.
- ========================================
-
- Nous venons de voir :
-
- 1° Un pointeur permet de mémoriser l'adresse d'une variable ordinaire et
- donc de la modifier, nous avons utilisé cette propriété pour passer des
- arguments à une fonction.
- 2° Il peut prendre la valeur symbolique NULL qui représente le zéro.
-
- Mais aussi:
-
- 3° Un pointeur peut prendre la valeur d'un autre pointeur, quand ils
- pointent des objets de même type.
- 4° On peut retrancher ou ajouter une valeur entière à un pointeur.
- 5° On peut calculer la différence de deux pointeurs lorsqu'ils pointent
- des éléments d'un même tableau.
-
- D'une manière générale on peut faire avec des pointeurs des opérations
- qu'il semble raisonnable de faire avec des adresses.
- La multiplication ou de la division de deux adresses semblent a priori
- des choses peu raisonnables. La plupart des compilateurs rejettent les
- opérations incorrectes.
- Chapitre 8: Des pointeurs page 3
-
-
-
- 2.1. Affectation de pointeurs:
- -------------------------------
- ************
- 2.1.1. Affectation de références de pointeurs: CH08_04.C
- ************
-
- 1° On déclare un entier et deux pointeurs sur des entiers. L'un des
- pointeurs n'est pas initialisé aussitôt et on lui affecte la constante
- symbolique NULL, pour signaler qu'il n'est pas lié à une variable.
-
- Nous avons déclaré des variables de type entier. Le compilateur réserve
- pour chacune, une zone mémoire de 2 octets. Pour des variables de type
- double, il aurait réservé des zones de 8 octets. On ne pourra donc pas
- mélanger les types, associer des variables sur 8 octets avec des pointeurs
- sur 2 octets pour stocker les mêmes valeurs.
-
- 2° Affectation de la référence de *pb à celle de *pa: pa= pb;
-
- Les adresses pointées, exprimées par les références, sont maintenant
- les mêmes, celle de la variable "b" attribuée par le compilateur au
- moment de sa déclaration et qui peut changer à chaque compilation.
-
-
- Il sera possible d'agir sur la variable "b" par l'intermédiaire des
- pointeurs *pa ou *pb. En effet, si on donne à *pa la valeur 32, on
- constate que b et *pb prennent la même valeur.
-
- ************
- 2.1.2. Affectation de pointeurs: CH08_05.C
- ************
-
- * Faisons l'état des lieux avec printf():
-
- Les deux pointeurs ont des valeurs différentes et pointent des
- variables différentes. Notez que la référence pa du pointeur contient
- l'adresse de "a" et que cette adresse est à 8 octets plus loin dans
- la mémoire que celle de "b". C'est normal puisque la taille d'un
- double est de 8 octets. Si nous avions utilisé des variables de type
- int le décalage n'aurait été que de 2 octets.
-
- * Le pointeur *pb est affecté à *pa. Quelles en sont les conséquences?
-
- Les deux pointeurs ont la même valeur et pointent deux variables
- différentes a et b puisque leur référence respective ne change pas.
-
- En somme, l'affectation d'une référence à une autre permet de pointer
- la même variable et l'affectation d'un pointeur à un autre de rendre
- égales les variables qu'ils pointent.
- Chapitre 8: Des pointeurs page 4
-
-
- ************
- 2.2. Incrémentation et décrémentation de pointeurs: CH08_06.C
- ---------------------------------------------------- ************
-
- 1° L'incrémentation de *pe se fait en utilisant des parenthèses car
- l'opérateur (++) a la priorité sur l'opérateur d'indirection (*),
- consultez le paragraphe 6. du chapitre 3.
-
- Nous avons écrit, (*pe)++ incrémenter le pointeur *pe.
-
- puis: *pe++; pe a d'abord été incrémentée
- puis déréférencée.
-
- * Dans le premier cas le pointeur pointe la variable "entier" puisque
- la référence ne change pas. Leur valeur commune est incrémentée.
-
- * Dans le second cas la référence pe est incrémentée de la valeur d'un
- "int", soit 2 octets.
-
- Quelle adresse, si s'en est une, allons nous y trouver?
- Totale incertitude, la porte de l'enfer des programmeurs est ouverte!
- Notre pointeur *pe va désormais pointer l'inconnu.
- Ma machine me donne:
- entier: 17 , *pe: 6814 , pe: 6736
-
- Ce qui veut dire qu'à l'adresse 6736 de la mémoire on trouve la
- valeur 6814.
-
- C'est donc dangereux quand on utilise des variables normales parce
- qu'on ne sait pas ce qui suit dans la mémoire. Par contre dans
- les tableaux on sait parfaitement ce qu'il y a dans la zone qui suit
- et les pointeurs deviennent le moyen d'accès privilégié aux valeurs
- qu'ils contiennent. Nous les utiliserons largement dans le chapitre
- qui leur est dédié.
-
- 2° Nous venons de voir les effets de l'incrémentation de la référence.
- Si nous décrémentons maintenant la référence pe, nous revenons à la valeur
- initiale et nous pointons donc à nouveau la variable "entier" de valeur 17.
-
- Chapitre 8: Des pointeurs page 5
-
-
-
-
- 3. QUELQUES POINTS PARTICULIERS:
- =================================
-
- 3.1. Pointeurs sur des fonctions.
- ---------------------------------
-
- C'est une catégorie particulière de pointeurs qui comme les pointeurs
- que nous connaissons permettent d'accéder à des adresses mémoire. Mais
- dans ce cas les adresses sont celles de fonctions. Nous allons voir dans
- un exemple simple le mécanisme de l'emploi de ces pointeurs.
-
- Nous étudierons plus loin de nouveaux objets, les structures, qui
- permettent d'associer des variables et des pointeurs, donc aussi, en
- utilisant ces pointeurs particuliers, des fonctions. C'est le point
- de départ en C de la programmation orientée objet et sans que nous
- soyons obligés d'ajouter une nouvelle couche de soft comme en Basic
- ou en Pascal.
-
- ************
- Analyser : CH08_07.C
- ************
-
- Le pointeur, *pointeur_sur_fonction, pointe une fonction à un seul
- paramètre de type double. Le nom du pointeur est entre parenthèses sinon
- le système l'interpréterait comme le prototype d'une fonction qui retourne
- un pointeur spécial dont le type est void .
-
- Le nom d'une fonction peut être considéré lui même comme un pointeur
- dont la référence est l'adresse de la fonction. D'une certaine manière
- on pourrait écrire:
-
- Affiche_1() est une fonction, Affiche_1 son adresse, sa référence.
- (*pointeur_sur_fonction) est un pointeur sur la fonction.
- &(*pointeur_sur_fonction) équivalent à pointeur_sur_fonction est
- sa référence, qui devient par l'affectation l'adresse de la fonction
- Affiche_1().
-
- Un pointeur sur une fonction, comme un pointeur sur une variable, permet
- d'appeler indirectement la fonction:
-
- Dans notre programme, le pointeur est utilisé pour appeler successivement
- trois fonctions d'affichage. Nous avons commencé chaque fois par écrire le
- code normal avant d'utiliser ce pointeur.
- De même qu'un pointeur ordinaire permet de "remplacer" une variable, un
- pointeur sur une fonction permet de "remplacer" la fonction, mais ces
- pointeurs ne peuvent être incrémentés.
- Chapitre 8: Des pointeurs page 6
-
-
-
- 3.2. Constantes et pointeurs.
- -----------------------------
-
- Un élément défini comme constant ne peut plus être modifié.
- Deux cas sont possibles avec les pointeurs:
-
- 1° int *const pa; le pointeur pointera toujours la même adresse.
- mais le contenu de cette adresse pourra être modifié.
-
- 2° const int *pa; ou bien
- int const *pa; le pointeur pourra pointer des adresses différentes
- mais les valeurs pointées ne seront pas modifiables.
-
- 3.3. Une fonction peut renvoyer un pointeur.
- --------------------------------------------
-
- double* fonction( int*, double);
-
- Ce prototype est celui d'une fonction qui reçoit comme arguments un
- pointeur sur un entier et un réel double. Elle renvoie un pointeur sur
- une variable de type double, donc une adresse et il sera possible d'agir
- sur cette variable à partir de ce pointeur.
-
- Vous trouverez plus loin un exemple avec le programme CH13_03.C du
- développeur français William Marie.
-
- 3.4. Pointeur void.
- -------------------
- C'est un pointeur dont le type n'est pas défini.
- Il possède une référence mais comme à cette adresse on ne connait pas la
- taille de l'enregistrement à lire , la lecture est impossible. Il faut
- donc lui imposer un type avant de l'utiliser.
-
- un exemple est donné par la fonction malloc() que nous étudierons vers la
- fin du manuel.
- On crée au préalable un pointeur, par exemple double *ptr; pointeur de
- type double. La fonction malloc() nous reserve un espace de mémoire pour
- une variable de type double et nous renvoie un pointeur dont la référence
- est l'adresse de cette zone. Nous affectons sa référence à ptr.
- La fonction est apte à répondre à tous les types de variables car le
- pointeur qu'elle renvoie est lui même de type void.
- Il suffit d'imposer à ce pointeur une conversion de type pour pouvoir
- légalement l'affecter au notre.
-
- void* malloc( unsigned int taille);
-
- Cette fonction utilise un argument qui est la taille de la mémoire à
- réserver et renvoie un pointeur void de type non défini.
- Nous écrirons notre code de la manière suivante:
-
- double *ptr;
- ptr= ( double*) malloc( taille); le pointeur de type void qui est
- retourné est converti en double.
-
-
- Fin du chapitre 8: Des pointeurs page 7
-
-