C_facile : Introduction au langage C
Cours

"adresse de" et "contenu de"

Les deux signes '&' et '*' ont une signification particulière quand ils sont placés devant un identificateur de variable. Ils sont liés à l'usage des variables de type pointeurs qui ne serviraient à rien s'il n'y avait pas la possibilité d'accéder à l'adresse d'une variable car on ne pourrait pas initialiser la valeur d'une variable de type pointeur.

Il s'agit de deux opérateurs unaires. Le '&' permet de récupérer la valeur de l'adresse du premier octet d'une variable, le '*' permet d'utiliser la valeur d'une adresse (premier octet d'un paquet d'octets) pour accéder au paquet d'octets dans son ensemble.

ExempleExemple : utilisation de pointeurs

int i =17;

      // Declare une variable entiere (supposons codee sur 4 octets)

int *pt_i ;

      // Declare une variable de type pointeur sur un entier, le contenu

      // sera une valeur qui correspondra à l'adresse memoire d'un octet

      // a ce stade, i est initialisee avec la valeur 0, mais pt_i n'est pas initialisee.

pt_i = &i;

      // Initialise le pointeur avec l'adresse du premier octet de la variable i

      // a ce stade pt_i contient l'adresse d'un unique octet MAIS le compilateur sait que

      // pt_i est une variable de type pointeur sur entier.

*pt_i = *pt_i+1;

      /* Effectue un calcul sur la variable pointée par pt_i, c'est-a-dire sur i lui-même, puisque pt_i contient             l'adresse de i. */

      /* A ce stade, i ne vaut plus 0, mais 1. */

Détail de l'instruction *pi = *pi+1.

La figure qui suit montre une carte possible de la mémoire centrale utilisée par le programme. C'est un extrait du ruban d'octet qui constitue la mémoire centrale. Nous allons ici considérer qu'un entier est codé sur 4 octets consécutifs. A droite de l'opérateur d'affectation = nous avons une expression *pt_i +1.

La variable pt_i contient une valeur de type adresse mémoire (elle a été déclarée comme un pointeur vers un entier). L'opérateur unaire * signifie que l'on va utiliser la valeur de l'adresse mémoire (du premier octet de l'entier) pour récupérer la valeur d'un entier qui est codé dans les 4 octets consécutifs.

Puisque pt_i contient la valeur de l'adresse du premier octet qui correspond à l'emplacement mémoire où se trouve rangée la valeur de l'entier i et que le compilateur sait que c'est un pointeur vers un entier, nous allons récupérer exactement les 4 octets consécutifs qui contiennent la valeur de l'entier i.

Une illustration de la « carte » de la mémoire utilisée par le programme
Une illustration de la « carte » de la mémoire utilisée par le programme[Zoom...]

A ce stade *pt_i vaut donc 0 dans l'expression *pi+1. Nous ajoutons ensuite 1.

La valeur de l'expression est donc 1. A gauche de l'opérateur d'affectation nous avons *pt_i. Cette fois-ci, puisque nous sommes à gauche de l'opérateur d'affectation, l'opérateur unaire (*) signifie que l'on va utiliser la valeur de l'adresse mémoire (premier octet de l'entier) pour ranger la valeur d'un entier (c'est donc 1) dans les 4 octets consécutifs.

En résumé le contenu de la variable i est 1 désormais. En d'autres termes *pt_i = *pt_i +1 est équivalent à i = i+1, sauf que nous avons utilisé une indirection via une adresse pour réaliser l'opération.

Attention

Il est très important de s'assurer que les pointeurs que l'on manipule sont initialisés correctement et ne contiennent pas n'importe quoi.

En effet, accéder à une zone mémoire via un pointeur non initialisé revient à lire ou à écrire dans la mémoire à un endroit inconnu et non maîtrisé (selon la valeur initiale du pointeur lors de sa création).

Complément

En général, on initialise les pointeurs dès leur création, ou, s'ils doivent être utilisés ultérieurement, on les initialise avec le pointeur nul (NULL). Cela permettra de faire ultérieurement des tests sur la validité du pointeur ou au moins de détecter les erreurs.

Le pointeur nul se note NULL. C'est une valeur particulière qui est définie dans le fichier d'en-tête stdlib.h. En C, elle représente la valeur d'une adresse invalide.

Attention

Il ne faut pas confondre l'utilisation du signe (*) lors de la déclaration d'une variable de type "pointeur vers " et l'utilisation du signe (*) au sein d'une instruction.

Dans le premier cas, cela permet d'avertir le compilateur : la valeur (une adresse mémoire) de la variable de type "pointeur vers" sera utilisée pour manipuler autant d'octets que nécessaires (la taille du réceptacle) pour coder le type de la variable qui est pointée.

Dans le second, cela permet de manipuler, effectivement, tout le paquet d'octets en utilisant l'adresse du premier. Déclarer le type de ce qui est pointé permet de vérifier que les opérations effectuées sont compatibles avec ce type. Il est à présent facile de comprendre pourquoi il faut spécifier le type de donnée qui est pointée.

Exemple

Considérons les déclarations suivantes :

int *pt_1 ;

      // variable de type pointeur vers un entier

char *pt_a;

      // variable de type pointeur vers un caractère

float *pt_z;

      // variable de type pointeur vers un float

Il faut interpréter correctement ces déclarations. Les identificateurs pt_1, pt_a et pt_z sont des identificateurs de variables.

Ces variables contiennent des valeurs qui sont des adresses d'octets, ce sont des pointeurs.

En précisant le type de la valeur pointée, le compilateur peut déterminer le nombre d'octets consécutifs à utiliser pour les opérations de récupération d'information ou de recopie d'information. C'est-à-dire la taille de la donnée qui est automatiquement pris en compte.

Remarque

Lors d'une déclaration, il faut répéter l'étoile dans la déclaration de plusieurs pointeurs : int *a, *b, *c,*d; .... ce qui signifie que syntaxiquement a, b, c et d sont des pointeurs d'entiers. Donc *a, *b, *c et *d sont des entiers quand on utilise cette syntaxe DANS une instruction.

Si l'on écrit : int *a, b, c, d;.... Selon cette déclaration, seul a est un pointeur d'entier, les autres sont des entiers.

Attention

Considérons le code suivant :

int i = 58;

      // Déclare une variable entière (supposons codée sur 4 octets)

int *pt_1 ;

      // Déclare une variable de type pointeur sur un entier, le contenu

      // sera une valeur qui correspondra à l'adresse mémoire d'un octet

      // A ce stade, i est initialisée avec la valeur 58, mais pt_1 n'est pas initialisée.

pt_1 = &i;

      // Initialise le pointeur avec l'adresse du premier octet de la variable i

      // A ce stade pt_i contient l'adresse d'un unique octet MAIS le compilateur sait que c'est

      // Une variable de type pointeur sur entier.

pt_1 = pt_1+1;

      // augmente de 1 valeur de la variable pointeur (c'est ce que l'on croit ...)

*pt_1 = *pt_1 – 58;

Ces instructions ne provoqueront pas d'erreur syntaxique à la compilation. Cependant, nous commettons une erreur. La valeur de pt_1 est initialisée avec la valeur de l'adresse du premier octet de l'entier i codé sur 4 octets consécutifs.

Augmenter la valeur de la variable pt_1 va faire que l'on ne pointera plus sur le premier octet. En toute logique on pourrait se dire que l'on pointe sur le deuxième octet (à cause du +1), puisqu'ils sont consécutifs.

Ce raisonnement est faux. En fait, la nouvelle valeur de pt_1 est égale à l'ancienne plus 4 octets de décalage.

A la compilation le compilateur possède l'information sur la taille dévolue au stockage d'un entier (nous avons supposé ici que c'était 4). Puisque la variable pt_1 est déclarée comme un pointeur vers un entier, le compilateur va supposer que l'on veut passer à l'entier suivant. Il va ainsi augmenter la valeur de pt_1 de la quantité 4.

Complément

En résumé l'instruction :

pt_1 = pt_1 + 1;

Compilé par le compilateur C, va générer l'instruction suivante à destination du micro processeur: pt_1 = pt_1 + 1*sizeof(int)

Cette génération est totalement transparente pour le programmeur qui doit savoir intérpréter cette instruction. Nous avons dit qu'il existait une arithmétique entière et une arithmétique flottante, il existe également une arithmétique particulière sur les pointeurs.

Elle tient compte de la taille de la donnée qui est pointée, c'est-à-dire du type de donnée utilisée pour déclarer la variable "pointeur vers".

Problème de conversion implicite et conversion explicite (page suivante)Affectation composée (page Précédente)
AccueilImprimer creativecommons : by-nc-ndRéalisé avec SCENARI