4. Pointeurs et allocation dynamique
•
Définitions et déclarations
•
Tableaux et pointeurs
•
Structures
•
Tableaux multidimensionnels
Pointeurs
• Chaque variable a une adresse en mémoire qu’il peut être utile de connaître.
• Pour obtenir cette adresse : opérateur “adresse de“
&
p = &i ;
affecte adresse de i dans p• Exemple :
void main ()
{ int i = 7 ;
printf(“ valeur de i = %d\n “, i);
printf(“ adresse de i = %p\n “, &i);
}
• Résutat :
> valeur de i = 7
> adresse de i = 0x03F0 (1008(10))
542 7 24
@ memoire:
0x03EC 0x03F0 0x03F4
i
•
Un pointeur est une variable pouvant contenir une adresse mémoire.
Donc
p = &i ;
affecte l’adresse de i à la variable p On dit que p “pointe“ désormais sur i.•
* : opérateur d’indirection ou de déréférencement;
Appliqué à un pointeur, donne accès à l’objet pointé.
Donc
j = *p ;
j = objet pointé par p j = i;/* i et *p interchangeables*/
Pointeurs
&i p
&i p
7
* i
Pointeurs
§ Déclaration :
en fonction du type de l’objet pointéEntier : int *i; /* car *i est un entier */
Réel: float *f ; Caractère: char *c;
q Exemple issu du K&R:
int x = 1, y = 2, z[10];
int *pi; /* pi est un pointeur sur 1 int */
pi = &x; /* pi pointe maintenant sur x */
y = *pi; /* y vaut désormais 1 */
*pi = 0; /* x vaut désormais 0 */
pi = &z[0]; /*pi pointe désormais sur z[0]*/
&?
i
&?
pi
&x pi
1 x
&z[0]
pi
? ?
z[0] z[1]
&x pi
0
* x
*
*
Pointeurs
Opérations sur les objets pointés : les opérations permises sur l’objet int *pi = &x;
*pi = *pi + 2; /* *pi et x ici interchangeables */
/* <-> x = x + 2 */
y = *pi – 1; /* <-> y = x – 1 */
(*pi)++; /* idem. (*pi) += 1; <-> x++ */
Opération sans indirection : car les pointeurs sont aussi des variables int *qi, *pi;
avant:
qi = pi ; /* qi pointe sur même objet que pi */
après:
&e qi
&f pi
&f qi
&f pi
Pointeurs
• Initialisation : int a,*pi;
(1) pi=&a;
*pi=3; //ok
(2) avec malloc() (voir plus loin)
§ Ne pas utiliser un pointeur non initialisé par l’adresse effective d’une variable !!
§ Déclarer un pointeur réserve un espace mémoire pour stocker une adresse
mais en aucun cas pour l’objet pointé.
int *pi;
*pi=3; /* pointeur non initialisé, contient une & aléatoire*/
/* et pas de mémoire réservée pour stocker l’entier pointé*/
Attention: ce code ne fait pas d’erreur à la compilation mais une erreur à l’exécution (Segmentation Fault)
&a pi
? a
&a pi
3
* a
&?
pi
? a
&?
pi *
&?
pi
Pointeurs
xkcd.com
Tableaux et pointeurs
•
Par définition: si pa est un pointeur sur un élément d’un tableau alors,
•
pa+i pointe sur le i-ème élément du tableau après pa
•
pa-i pointe sur le i-ème élément avant pa
int *pa, a[5];
pa = &a[0]; /*pa point sur l’élément 0 de a*/
Note : si pa contient 0x10, l’adresse du premier élément, alors
pa+1 ou p++ vaut 0x14 dans le cas où l’entier signé est codé sur 4 octets.
&a[0]
pa
a[0] a[1] a[2] a[3] a[4]
? ? ? ?
?
pa+1 pa+2 …
Tableaux et pointeurs
Ainsi a[i] identique à *(pa+i), si pa pointe sur le premier élément:
*(pa+2)=12; /* idem. a[2]=12*/
*pa = *(pa+2);
/* noter l’importance des parenthèses */
*pa = *pa + 2 ;
&a[0]
pa
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
?
pa+1 pa+2 …
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
&a[0] 12 pa
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
&a[0] 14 pa
Tableaux et pointeurs
•
Le nom d’un tableau vaut l’adresse de son 1er élément: a ≈ &a[0]
Donc , *(a+i) identique à a[i]
int a[5];
*a=7; /* affecte 7 à a[0] */
*(a+1)=2; /* idem a[1] = 2 */
int *pa = a; /* idem. pa = &a[0]; */
Note : pa est une variable donc pa = a; puis pa = b; et pa++; VALIDES, mais a est juste un nom donc a = pa; et a++ ; INVALIDES.
a pa
a[0] a[1] a[2] a[3] a[4]
? ? ? ?
?
a+1 a+2 … a
a[0] a[1]
2 7
a+1 a
pa+1
? fondamental!
Tableaux et pointeurs
aussi : a+i identique à &a[i]
*(a+2)=12; /* idem. a[2]=12*/
*a = *(a+2);
/* noter l’importance
des parenthèses sinon :*/
*a = *a + 2 ;
a
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
?
a+1 a+2 …
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
12
a[0] a[1] a[2] a[3] a[4]
? 12 ? ?
14 a
a
Allocation dynamique de la mémoire
¨
Allocation statique: Taille d'un tableau doit être une constante.
• int tab[50];
• int tab[]={3,4,5,6} ( ≈ tab[4] )
• int tab[n] interdit ! (bien qu’une norme et des compilateurs récents l’autorisent maintenant L)
•
Or la taille n’est parfois pas connue à l’avance.
Ø
Surdimensionner le tableau (ex: tab[4000] )
mais probablement un gâchis de mémoire inutilisable
Ø
Définir la taille pendant l’exécution (Allocation dynamique) :
1. une fonction malloc() recherche un bloc mémoire libre contigüe de taille désirée et renvoie son adresse.
2. cette adresse est affectée à un pointeur pour utilisation du bloc mémoire.
Allocation dynamique de la mémoire
¨
Allocation dynamique: malloc()
§
malloc (int size) : recherche un bloc contigüe de size octets libre en mémoire et retourne l’adresse de début du bloc.
Retourne le pointeur NULL , si échec (pas assez de mémoire libre).
int n=4, *tb;
tb = (int*) malloc ( n*sizeof(int) );
Accès (similaire au tableau statique) :
tb[i] ou *(tb+i)
&bloc ? ? ? ?tb
&?
tb
tb[0] tb[1] … tb+1 …
? ? ?
&bloc ?
Allocation dynamique de la mémoire
¨
Allocation dynamique:
§
NULL est un symbole constant valant 0, défini dans stdlib.h.
§
sizeof(type) : renvoie la taille en octets de l’objet de type type
§
malloc() renvoie un pointeur de type void*.
§
void *ptr; définit un pointeur générique, i.e dont le type de l’objet pointé est indéfini. Avant utilisation, le convertir explicitement:
int *pi;
pi=(int*) ptr; /* pi contient la même adresse que ptr */
/* mais pi pointe sur un entier de taille sizeof(int)
et pi+1 sur l’entier suivant */
Allocation dynamique de la mémoire
#include <stdio.h>
#include <stdlib.h> /* pour le prototype de malloc() */
int main(void) { int n, *tab;
printf (“Taille désirée?“);
scanf (“%d“,&n);
tab=(int*) malloc (n*sizeof(int)); /* allocation dyn. de n entiers */
if (tab==NULL) /* test si échec de l’allocation */
{ printf(“erreur allocation tab“);
return(-1);} /*sort du programme si échec!*/
tab[0]=1; /* idem. *tab=1; */
free(tab); /* libération du bloc mémoire alloué par malloc*/
return(0); }
Allocation dynamique de la mémoire
Libération de la mémoire allouée par malloc() :
§
free(void *ptr) : libère le bloc mémoire d’adresse ptr
précédemment alloué par un malloc .
§
Toujours libérée la mémoire dès que l’on en a plus besoin (en fin de programme ?)
§
La mémoire libérée en cours d’exécution peut être réutilisée pour d’autres allocations.
•
Note: malloc et free peuvent être utilisés pour allouer un bloc mémoire
une variable du type pointé (un tableau d’1 seul élément).
Erreur de Segmentation
Segmentation de la mémoire :
§
Le système d’exploitation alloue de la mémoire à chaque programme.
§
Un programme ne peut accéder (lecture/écriture) à la mémoire d’un autre programme par sécurité: un programme avec des erreurs ne peut ainsi corrompre un autre programme.
Erreur de Segmentation (Segmentation Fault):
§
Si un programme tente d’accéder de la mémoire qui ne lui appartient pas
Ø
détection par le système de la violation mémoire qui envoie un signal (SIGSEGV) au programme
Ø
avec pour résultat de stopper immédiatement l’exécution du programme
Erreur de Segmentation
Cause courante d’erreur de segmentation :
§ Avec scanf (““, void *) dont les arguments sont des adresses où écrire les donnés lues au clavier,
int n; /*n non initialisé*/
scanf (“%d“, n); /*adresse de stockage:
contenu aléatoire de n */
/* correct : scanf (“%d“,&n) -> adresse : adresse de n*/
§ Utiliser un pointeur non initialisé (déjà vu)
int *pi; /* pi contient une adresse aléatoire */
*pi = 3; /* accès en écriture à une adresse aléatoire: risque de Seg Fault*/
&?
pi * ?
?
Erreur de Segmentation
Cause commune d’erreur de segmentation :
§ Débordement de tableau statique ou dynamique
int a[2];
a[2] = 3; /* idem *(a+2)=3 */
*(a - 1) = 1;
Note: parfois pire (pas de segmentation fault) mais un programme bogué
int N=2, a[2];
a[2] = -1; /* hors tableau et tombe sur N*/
N++; /* N vaut 0 !!*/
si le hasard fait que l’occupation mémoire est:
a
a[0] a[1]
? ?
a+1 a+2 a-1
? ?
a
a[0] a[1] N
? ?
a+1 a+2
?
Les Structures
regroupe des éléments de types différents
facilite l’organisation et la manipulation de données composées
un composant est appelé un champ ou un membre
déclaration de structure: struct nom_facultatif_pour_la_struct
{ définition };
struct coureur {
char nom[30] ;
char prenom[30] ; int dossard ;
float temps ;
};
struct coureur c1, c2, c3 ;
/* c1, c2, c3 sont des structures de type coureur */
idem:
struct coureur {
char nom[30] ; char prenom[30];
int dossard ; float temps ; }c1, c2, c3;
Les Structures
Initialisation à la déclaration :
struct coureur c1={“Bolt“,“Usain“,23,9.58};
accès aux champs par l’opérateur “ . “
c1.temps = 9.54 ; c2.dossard = 2 ;
structures et pointeurs
struct coureur *c4 ; /*n’alloue pas de mémoire pour la structure*/
c4 = (struct coureur *)malloc(sizeof(struct coureur));
(*c4).temps = 42.54 ; /* parenthèse nécessaire */
c4->temps = 42.54 ; /* idem avec opérateur “->“
pour alléger l’écriture */
typedef
permet de définir des noms de nouveau type de données
Exemple : typdef int Longueur;
/*rend Longueur synonyme de int*/
Longueur i, *j; /* définit 1 variable et 1 pointeur (sur entier)*/
allège les déclarations de structure
typedef struct { char nom[30] ;
char prenom[30] ; int dossard ;
float temps ; } coureur ;
/* désormais on peut omettre le mot-clé struct :*/
coureur c1 , c2 , *c3 ;
c3 = (coureur*) malloc (1*sizeof(coureur));
Segmentation Fault
Tableaux multidimensionnels
•
Un tableau à 2 dimensions (L,C) est un tableau de tableau :
un tableau L éléments (/lignes) contenant chacun un tableau de C éléments.
•
Déclaration : int a[L][C];
L lignes de C éléments
int a[2][3]={{1,2,3},{4,5,6}};
avec initialisation•
on se permet de le représenter ainsi pour plus de lisibilité :
a[1][0] a[1][1] a[1][2]
4 5 6
1 2 3
a[0][0] a[0][1] a[0][2]
a[1]
a[0]
a:
a[1][0] a[1][1] a[1][2]
4 5 6
1 2 3
a[0][0] a[0][1] a[0][2]
a[0]
a[1]
Tableaux multidimensionnels
•
Comme pour un tableau 1D : a ≈ &a[0][0]
•
( a[0] est le nom du tableau de la première ligne , a[0] ≈ &a[0][0]
et a[1] ≈ &a[1][0] )
•
Accès aux composantes :
a[i][j]
(à préférer J)
ou *( a[i] + j ) ou encore *( *(a+i) + j )
a[1][0] a[1][1] a[1][2]
4 5 6
1 2 3
a[0][0] a[0][1] a[0][2]
a a[0]
a[1]
a[1]+1
Tableaux multidimensionnels
#include <stdio.h>
void main(void)
{ int m[2][3] = {{11,12,13},{21,22,23}};
*((*m)+1) = -2;
m[1][0]= -1;
printf("%i %i %i \n",
m[0][0], m[0][1], m[0][2]);
printf("%i %i %i \n",
**(m+1), *(*(m+1)+1), *( m[1]+2));
}
Sortie écran:
11 -2 13 -1 22 23
Le tableau de pointeurs !
char s[] = "On"; /* idem. char s[3]= .. */
char s1[] = "est";
char s2[] = "jeudi";
Si on souhaite mettre les chaines dans un tableau :
un tableau avec les adresses du 1er élément des chaînes (le noms des chaînes) ≈ un tableau de pointeurs vers des caractères
char * tabs[] = {s, s1, s2};
/* idem ….= { &s[0], &s1[0], &s2[0] } */
tabs[1][2] = x;
/* car idem *( *(tabs+1) + 2)=… */
s
s2 s1
e s t \0
O n \0
j e u d i \0
s s1 s2
s[0] s[1] s[2]
e s x \0
O n \0
j e u d i \0
s[0] s[1] s[2]
tabs
Le tableau de pointeurs !
Accès au caractères (toujours pareil):
tabs[i][j] ou *( *(tabs+i)+j )
car tabs[i] ou *(tabs+i) est l’adresse du 1er élément de la chaine.
On peut définir un pointeur sur le tableau de pointeurs
(è pointeur sur un pointeur sur 1 caractère):
char ** ptr_s = tabs;
L’accès (toujours pareil) :
ptr_s[1][2] = X;
ou
*(*(ptr_s+1)+2) = X ;
e s X \0
O n \0
j e u d i \0
s[0] s[1] s[2]
tabs tabs
ptr_s
s s2 s1
Tableau de pointeurs sur entiers !
int a[3], b[4], c[3];
int * mat[3] = {a,b,c};
(
int ** ptr_m = tab;)
Le tableau int* mat[3] peut être alloué dynamiquement puis remplit avec les noms (adresses) des tableaux:
int **ptr_m;
ptr_m = (int**) malloc ( 3*sizeof(int*) );
ptr_m[0] = a;
…
On peut encore allouer les tableaux a, b et c dynamiquement: tableau 2D dynamique.
7 2 11 1
1 -4 0
-4 6 9
a[0] a[1] a[2]
mat tab
ptr_m
a
c b
7 2 11 1
1 -4 0
-4 6 9
&bloc ptr_m
a
c b
( )
Tableaux multidimensionnels dynamiques
•
Allocation dynamique : d’un tableau de dimension ( 3 , 4 ) 1. Allocation d’un tableau de 3 pointeurs sur des entiers int ** ptr_m;
ptr_m = (int**) malloc(n*sizeof(int *));
2. On alloue 3 tableaux de 4 entiers dont on stocke les adresses dans le tableau de pointeurs avec malloc()
&bloc’ptr_m[0][0]
&bloc ptr_m
&bloc’
&bloc’’’
&bloc’’
? ? ? ?
? ? ? ?
? ? ? ?
&bloc ptr_m
&?
&?
&?
? ? ? ?
Tableaux multidimensionnels
void main(void)
{ int i; int Lignes = 3,Colonnes = 4;
int ** ptr_m; /* pointeur sur un pointeur */
ptr_m = (int**) malloc(sizeof (int*) * Lignes);
/* allocation du tableau de pointeurs */
for (i = 0; i < Lignes; i++)
ptr_m[i] = (int*) malloc(sizeof (int) * Colonnes);
/* allocation des tableaux d’entiers (chq ligne)*/
ptr_m[1][3] = 77;
for (i = 0; i < Lignes; i++)
free (ptr_m[i]); /* libération du tableau de chaque ligne */
/* ou avec free(mat[i]); */
free(ptr_m); /* libération du int** */
}
Tableaux multidimensionnels
•
Autre solution: considérer le tableau 2D comme un vecteur
•
Déclaration : int matrice [L*C] ;
ou int *matrice=(int*)malloc(L*C*sizeof(int));
•
Accès à l’élément correspondant à “ matrice[i][j]“ :
matrice[i*C+j] ou *(matrice + (i*C) + j)
1ère ligne 2ème ligne
1 2 3 4