Année universitaire 2002-2003 |
Licence d'informatique
|
La mémoire est découpée en octets. Chaque octet est repéré par son numéro d'ordre, ou adresse. Un pointeur "pointe" vers un octet en indiquant son adresse. En pratique, un pointeur est une variable qui contient une valeur de type "adresse" (qui est un type numérique entier correspondant au spécificateur de format %p). Une variable est un pointeur si et seulement si son nom est précédé, lors de sa déclaration, d'un ou de plusieurs caractères * (lesquels caractères ne font pas partie du nom de la variable).
La manipulation des pointeurs nécessite une bonne maîtrise des deux opérateurs suivants :
int a,b=0; /* À l'exécution, on suppose que le système alloue un espace mémoire à la variable b, situé par exemple à l'adresse 20000 */ int *p; /* La variable p peut contenir l'adresse d'un entier. Réservation en mémoire d'une zone pour stocker cette adresse. À ce moment-là de l'exécution, la variable p n'est pas initialisée. */ p=&b; /* La variable p pointe vers la zone où est stockée la variable b. À ce moment-là de l'exécution : p == 20000 */ a=*p; /* La variable a prend la valeur logée à l'adresse 20000, soit 0 */ |
p=NULL;
Les valeurs de type "adresse" sont représentées graphiquement par des flèches qui désignent l'objet pointé, excepté la valeur NULL qui doit apparaître en toutes lettres.
La taille de la zone mémoire pointée par un pointeur est donnée par le type de l'objet pointé. Par exemple, si on a la déclaration suivante :
char *ch;
alors ch est un pointeur vers une zone de 8 bits en mémoire. Dès lors, l'adresse de l'octet situé immédiatement après est ch+1. En incrémentant ch, il est ainsi possible de parcourir la mémoire d'octet en octet. Un pointeur p déclaré par short *p; pointe sur une zone pouvant contenir un entier court (codé sur 16 bits). En incrémentant p, on balaye la mémoire de 16 bits en 16 bits :
Soit la séquence :
int tab[10];
int *p,i;
for (i=0;i<10;i++) tab[i]=0;
i=5;
p=tab; /* Équivalent à : p=&tab[0]; */
p=tab+i; /* Équivalent à : p=&tab[i]; */
À l'aide d'une représentation graphique, indiquer les modifications apportées au tableau tab par la séquence d'instructions suivante, et ce après chaque instruction :
À la préparation de l'appel d'une fonction, une copie est faite de chaque paramètre. Tous les passages de paramètres sont strictement faits par valeur. Une fonction peut changer la valeur de la copie des paramètres, mais ces changements ne modifient pas la valeur des paramètres de départ. Cependant, il est possible de passer en paramètre un pointeur vers une zone mémoire où la fonction opérera des modifications. Ainsi, lorsqu'on passe un tableau en paramètre, on crée en fait une variable de type "pointeur" permettant d'accéder aux zones de la mémoire où sont rangés les éléments du tableau. Il existe donc deux différences importantes entre la variable de type "tableau" déclarée par int tab1[3] et le paramètre de type "tableau" déclaré par int tab2[] :
Lors de la séance 3 de travaux dirigés, on avait donné l'exemple d'une fonction lecture_int_C_incorrecte, qui lit un entier et le renvoie au programme principal, mais par l'intermédiaire d'un paramètre passé par valeur, ce qui rend une telle fonction incorrecte. Voici une version corrigée de cette fonction, dans laquelle c'est l'adresse du paramètre qui est passée par valeur :
#include <stdio.h> void lecture_int_C_correcte(int *p_a) { scanf("%d",p_a); (*p_a)++; } int main(void) { int a=0; lecture_int_C_bis(&a); printf("%d\n",a); return(0); } |
Si l'utilisateur tape la valeur 5, alors cela provoquera bien l'affichage de la valeur 6
Écrire une fonction qui permute le contenu de deux variables entières, ainsi qu'un exemple d'appel à cette fonction.
Une chaîne de caractères en C est forcément du type "pointeur de caractère". Le dernier caractère, qui indique la fin de la chaîne, doit toujours être le caractère \0, de code ASCII nul. Il existe deux manières de définir une chaîne de caractères :
Dans ce cas, nom_chaîne1 est une adresse de caractère et ne peut donc pas apparaître dans le membre gauche d'une affectation (c'est l'adresse d'une zone mémoire de TAILLE_CHAINE octets). On dit dans ce cas que l'espace mémoire nécessaire au stockage des caractères de la chaîne est réservé "statiquement". Pour stocker des caractères (composant une chaîne) dans cette zone mémoire réservée, si ces caractères sont tapés au clavier par l'utilisateur, on peut se servir des fonctions getchar ou scanf, déjà vues à plusieurs reprises :
Cette séquence présente l'avantage de pouvoir utiliser n'importe quel caractère pour marquer la fin de la lecture de la chaîne (ici, c'est \n). Il ne faut pas confondre ce caractère avec le caractère \0, qui marque la fin de la chaîne de caractères en mémoire, et qui est rajouté ici à l'aide de la dernière instruction de la séquence. Un autre avantage de cette séquence est qu'on peut surveiller le débordement éventuel de l'espace mémoire réservé.
Cette séquence rajoute automatiquement le caractère \0 à la fin de la chaîne stockée en mémoire. En revanche, elle présente l'inconvénient de ne pas laisser le choix du caractère qui marque la fin de la lecture de la chaîne. En effet, la lecture s'arrête au premier caractère séparateur rencontré (espace, tabulation ou retour-chariot). Un autre défaut de cette séquence est qu'elle ne permet pas de surveiller le débordement éventuel de l'espace mémoire réservé. Pour ces deux raisons, on préférera souvent la première séquence proposée (celle qui utilise la fonction getchar).
Dans ce cas, la variable nom_chaîne2 est un pointeur de caractère, qui peut apparaître dans le membre gauche d'une affectation, mais qui ne pointe pas vers une zone mémoire réservée. On verra dans la séance 5 de travaux dirigés quelle fonction permet de réserver une zone mémoire d'une certaine taille, et d'affecter l'adresse de cette zone à la variable nom_chaîne2. On dira dans ce cas que l'espace mémoire nécessaire au stockage des caractères de la chaîne est réservé "dynamiquement".
Écrire une fonction qui retourne la longueur d'une chaîne de caractères. Écrire un programme principal initialisant la chaîne de caractères (déclarée sous la forme d'une variable de type "tableau de caractères") et appelant cette fonction.