Pointeurs
Un pointeur est une variable de type référence, dont la valeur est l'adresse d'une autre variable appelée cible.
Sur un sytème 32 bits la taille d'un pointeur est de 4 octets (32 bits), sur un système 64 bits la taille d'un pointeur de de 8 octets (64 bits).
int score = 65040073;
int *pointeur = &score;
La déclaration d'un pointeur s'effectue en utilisant le symbole * appelé indirection.
Pour déclarer un pointeur il convient de spécifier aussi le type de la variable cible. Comme cela le système connait le nombre d'octets visés par le pointeur.
Dans cette déclaration le pointeur *pointeur vise une variable de type int.
L'adresses d'une variable correspond à l'adresse de début de la variable dans la mémoire.
int *pi; // pi est un pointeur pointant sur un entier
char *pc; // pc est un pointeur pointant sur un char
float *pf; // pf est un pointeur pointant sur un float
Opérateur Adresse de ...
L'opérateur & sur une variable existante permet de d'assigner au pointeur l'adresse de la variable cible.
Le format %p de printf permet d'afficher l'adresse mémoire en hexadécimal d'un pointeur.
printf("%p\n", pointeur);
Opérateur contenu de ...
Pour modifier la valeur d'une variable données en passant par le pointeur s'effectue en utilisant l'opérateur d"indirection *;
*pointeur = 66000000;
printf("%d\n", score);
l'opérateur d'indirection * modifie la valeur ciblée par le pointeur, et non le pointeur lui-même.
Allocation dynamique
La déclaration d'un pointeur n'engendre pas de réservation en mémoire.
Les allocations de mémoires pour les pointeurs sont réalisées dans une zone mémoire bien spécifique qui s'appelle le tas (heap) (cours INFO2).
-
L'allocation correspond à la réservation d'un bloc mémoire spécifique à votre programme par le gestionnaire de mémoire du système d'exploitation.
-
Cette opération est qualifiée d'allocation dynamique car la taille du bloc est modifiable à tout instant dans votre programme.
-
La gestion de l'allocation mémoire se fait grˆace aux 3 fonctions suivantes :
-
malloc et calloc : demande de mémoire du programme au système d'exploitation ;
-
realloc : changement de la taille du bloc mémoire alloué (en + ou en -) ;
-
free : libération du bloc mémoire lorsqu'il n'est plus utilisé.
Afin de pouvoir gérer la mémoire dynamiquement en utilisant ces fonctions, il faut inclure stdlib.h.
La déclaration d'un pointeur n'engendre pas de réservation mémoire. Si on ne réserve pas d'emplacement mémoire, il y a risque de débordement d'un pointeur dans les données voisines, donc de plantage du programme.
malloc
void *malloc(size t taille);
Elle permet d'allouer un bloc de mémoire de taille octets dans la zone de mémoire appelée tas (heap), réservée aux données ;
Le bloc de données n'est pas initialisé lors de l'allocation ;
Elle renvoie un pointeur de type void (permettant de gérer les adresses de données de tous types). Il convient donc de le convertir en un type de données déterminé (cast) ;
Si l'allocation réussit, la fonction renvoie un pointeur sur le bloc nouvellement alloué. Si la place disponible est insuffisante ou si taille vaut 0, elle renvoie un pointeur nul : NULL.
int *p;
p = (int *) malloc(10 * sizeof(int));
// réservation pour 10 entiers
if ( p== NULL) // test création du pointeur
{
printf("erreur d'allocation mémoire !!!");
// ici traitement de l'erreur ...
}
calloc
void *calloc(size t nombre, size t tailleType);
Elle permet d'allouer un bloc de mémoire de nombre ∗ tailleType octets dans la zone de mémoire appelée tas (heap), réservée aux données ;
Le bloc de données est initialisé avec des 0 lors de l'allocation ;
Elle renvoie un pointeur de type void (permettant de gérer les adresses de données de tous types). Il convient donc de le convertir en un type de données déterminé (cast) ;
Si l'allocation réussit, la fonction renvoie un pointeur sur le bloc nouvellement alloué. Si la place disponible est insuffisante ou si taille vaut 0, elle renvoie un pointeur nul : NULL.
int *p;
p = (int *) calloc(10 , sizeof(int) );
// réservation pour 10 entiers
if ( p== NULL) // test création du pointeur
{
printf("erreur d'allocation mémoire !!!");
// ici traitement de l'erreur ...
}
realloc
void *realloc(void *pointeurBase, size t newTaille);
Elle permet de changer la taille d'un bloc de mémoire déjà alloué. Elle est le reflet de l'aspect dynamique des pointeurs ;
Le paramètre newTaille correspond à l'addition de la taille de l'ancien bloc et de la taille du bloc supplémentaire à ajouter ;
Le contenu du bloc précédant est gardé ⇒ pas besoin de gérer le copie ;
Elle renvoie un pointeur de type void (permettant de gérer les adresses de données de tous types). Il convient donc de le convertir en un type de données déterminé (cast) ;
Si l'allocation réussit, la fonction renvoie un pointeur sur le bloc nouvellement alloué. Si la place disponible est insuffisante ou si newTaille vaut 0, elle renvoie un pointeur nul : NULL.
int *p;
p = (int *) realloc( p , 20 * sizeof(int) );
// réservation pour 10 entiers supplémentaires
if ( p== NULL) // test création du pointeur
{
printf("erreur d'allocation mémoire !!!");
// ici traitement de l'erreur ...
}
!!! ATTENTION, les données peuvent être déplacées si l'espace n'est pas suffisant !!!
Libération
La fonction free
void *free(void *pointeur);
Cette fonction permet de libérer l'espace mémoire alloué par les 3 fonctions précédentes.
Il est important de libérer l'espace après utilisation, sinon celui-ci devient inutilisable pour la suite du programme ! !
# include < stdio .h >
int main (int argc, char *argv[])
{
int *p;
...
free(p); // libération de la mémoire occupée
}
L'arithmétique des pointeurs
On peut déplacer un pointeur dans la mémoire à l'aide des opérateurs d'addition, de soustraction, d'incrémentation, de décrémentation.
On ne peut le déplacer que d'un nombre de cases mémoire multiple de la taille définit lors de la déclaration.
int *pi = malloc(100 * sizeof(int));
char *pc = malloc(100 * sizeof(char));
*pi = 5;
*pc = 'A';
*(pi+1) = 200; // 200 est de contenu de la case mémoire 4 octets après pi
*(pi+2) = 500; // 500 est le contenu de la case mémoire 8 octets après pi
*pc = 'A'; // la case mémoire pc contient le code ASCII de A = 65
pc--; // on décrémente la valeur du pointeur pc de 1
*pf = 1.5; // 1,5 est stocké dans la case mémoire pf et les 4
// suivantes
pf++; // on incrémente la valeur du pointeur pf de 4 cases
//mémoires qui correspond à la taille d'un float
int *pi,*qi,i;
pi = qi; // autorisé
i = pi + qi; // interdit : on ne peut pas additionner deux pointeurs
// ça n'a pas de sens
i = pi-qi; // autorisé : donne le nombre d'objets entre les deux
// pointeurs
I = pi*qi; // interdit : on ne peut pas multiplier deux pointeurs
// ça n'a pas de sens
Utilisation des pointeurs
Les pointeurs et les tableaux.
Le langage C gère un tableau comme un pointeur à la différence près qu'il réserve un emplacement dimensionné par la déclaration. Exemple : int T[50]; int i, *pi, T[10]; pi = &i; // *pi représente i car pi pointe sur i *pi = 0; // c'est équivalent à i = 0 pi = &T[0]; // pi pointe maintenant sur le premier élément du tableau T // *pi représente T[0] *pi = 0; // équivalent à T[0] = 0;
Tableaux.
La déclaration de T[50] réserve en mémoire 50 entiers, mais nous avons en même temps un nouveau pointeur initialisé sur le début du tableau.
int *pi, T[10], X;
pi = T; // pi pointe sur le début du tableau soit le premier élément
*T = 0; // c'est équivalent à T[0] = 0
*(T+2) = 5; // c'est équivalent à T[2] = 5
*(pi+5) = 0; // équivalent à T[5] = 0;
Les tableaux en mémoire = Tableaux de pointeurs
Exemple : tableau 1 dimension
char Tab1D[5];
Exemple : tableau 2 dimensions avec des chaines de caractères
char Tab2D [5][7] = {"UN", "DEUX", "TROIS", "QUATRE", "CINQ"};
On alloue le maximum pour ne pas avoir de problèmes de débordement. Zones Mémoire Perdues
On déclare un tableau de pointeurs dans lequel chaque pointeur désigne l'adresse d'un autre tableau Exemple : tableau 2 dimensions avec des chaines de caractères char *Tab2D [5] ;
char *Tab[] = { "UN" , "DEUX", "TROIS", "QUATRE", "CINQ"} ;
Tab[0] pointe sur "UN"
Tab[1] pointe sur "DEUX"
*Tab[0] retourne sur 'U' de "UN"
*( Tab[0] + 1) retourne sur 'N' de "UN"
*( Tab[1] + 2) retourne sur 'U' de "DEUX"
Attention:
*Tab[4] + 1 retourne 'D' car *Tab[4] = 'C' et 'C' + 1 = 'D'
Passage de paramètres aux fonctions
Exemple : Fonction qui prend un pointeur en paramètre d'entrée int carre(int *A) { int Res; Res = (*A) * (*A); // équivalent à : A**A ou * A * * A return (Res); } Void main(void) { int X,Y; X = (int)malloc(1sizeof(int)); // initialisation du pointeur sur le tas // (heap) zone mémoire réservée aux // données *X = 2; Y = carre(X); }
Les pointeurs avec une fonction. Exemple : Fonction qui prend un pointeur en paramètre d'entrée et retourne un pointeur.
void carre(int *A)
{
(*A) = (*A) * (*A); // équivalent à : *A**A ou * A * * A
}
Void main(void)
{
int *X;
X = 2;
carre(&X);
}