1
4. Pointeurs et allocation dynamique
• Définitions et déclarations
• Tableaux et pointeurs
• Structures
• Tableaux multidimensionnels
2
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);
}
@ memoire:
• Résutat :
> valeur de i = 7 0x03EC 24
> adresse de i = 0x03F0 (1008(10)) 0x03F0 7 i
0x03F4 542
3
Pointeurs
• Un pointeur est une variable pouvant contenir une adresse
mémoire.
Donc p = &i ; affecte l’adresse de i à la variable p &i
On dit que p “pointe“ désormais sur i. p
• * : 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
&i 7
j = i;
p i
/* i et *p interchangeables*/ *
4
Pointeurs
§ Déclaration : en fonction du type de l’objet pointé
Entier : int *i; /* car *i est un entier */ &?
Réel: float *f ; i
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 1
pi = &x; /* pi pointe maintenant sur x */
pi x
y = *pi; /* y vaut désormais 1 */ *
*pi = 0; /* x vaut désormais 0 */ &x 0
pi * x
pi = &z[0]; /*pi pointe désormais sur z[0]*/ &z[0] ? ?
pi z[0] z[1]
5
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; &f
&e
avant: qi pi
qi = pi ; /* qi pointe sur même objet que pi */
après: &f &f
qi pi
6
Pointeurs
• Initialisation : int a,*pi; &? ?
pi a
(1) pi=&a; &a ?
pi a
*pi=3; //ok 3
&a
pi a
*
(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é. &?
pi
int *pi;
*pi=3; /* pointeur non initialisé, contient une & aléatoire*/
/* et pas de mémoire réservée pour stocker l’entier &? pointé*/
pi
*
Attention: ce code ne fait pas d’erreur à la compilation
mais une erreur à l’exécution (Segmentation Fault)
7
Pointeurs
[Link]
8
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*/
pa+1 pa+2 …
&a[0]
pa
? ? ? ? ?
a[0] a[1] a[2] a[3] a[4]
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.
9
Tableaux et pointeurs
Ainsi a[i] identique à *(pa+i), si pa pointe sur le premier élément:
&a[0] pa+1 pa+2 …
pa
? ? 12 ? ?
*(pa+2)=12; /* idem. a[2]=12*/
a[0] a[1] a[2] a[3] a[4]
*pa = *(pa+2);
&a[0] 12 ? 12 ? ?
/* noter l’importance
des parenthèses */ pa a[0] a[1] a[2] a[3] a[4]
*pa = *pa + 2 ; &a[0] 14 ? 12 ? ?
pa a[0] a[1] a[2] a[3] a[4]
10
Tableaux et pointeurs
• Le nom d’un tableau vaut l’adresse de son 1er élément: a ≈ &a[0]
fondamental!
Donc, *(a+i) identique à a[i] a a+1 a+2 …
? ? ? ? ?
a[0] a[1] a[2] a[3] a[4]
int a[5];
pa+1
*a=7; /* affecte 7 à a[0] */
a a+1
*(a+1)=2; /* idem a[1] = 2 */
int *pa = a; /* idem. pa = &a[0]; */
a 7 2
pa a[0] a[1]
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.
11
Tableaux et pointeurs
aussi : a+i identique à &a[i]
*(a+2)=12; /* idem. a[2]=12*/ a a+1 a+2 …
? ? 12 ? ?
a[0] a[1] a[2] a[3] a[4]
*a = *(a+2);
/* noter l’importance a
des parenthèses sinon :*/
12 ? 12 ? ?
a[0] a[1] a[2] a[3] a[4]
*a = *a + 2 ; a
14 ? 12 ? ?
a[0] a[1] a[2] a[3] a[4]
12
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.
13
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
tb = (int*) malloc ( n*sizeof(int) );
&bloc ? ? ? ?
tb+1 …
Accès (similaire au tableau statique) :
tb[i] ou *(tb+i) &bloc ? ? ? ?
tb tb[0] tb[1] …
14
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 */
15
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); }
16
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).
17
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
18
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 ?
*
19
Erreur de Segmentation
Cause commune d’erreur de segmentation :
§ Débordement de tableau statique ou dynamique
int a[2]; a-1 a a+1 a+2
a[2] = 3; /* idem *(a+2)=3 */
*(a - 1) = 1; ? ? ? ?
a[0] a[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 !!*/ a a+1 a+2
si le hasard fait que l’occupation mémoire est: ? ? ?
a[0] a[1] N
20
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] ; idem:
char prenom[30] ; struct coureur {
int dossard ;
char nom[30] ;
float temps ; char prenom[30];
}; int dossard ;
float temps ;
struct coureur c1, c2, c3 ;
/* c1, c2, c3 sont des structures }c1, c2, c3;
de type coureur */
21
Les Structures
Initialisation à la déclaration :
struct coureur c1={“Bolt“,“Usain“,23,9.58};
.“
accès aux champs par l’opérateur “
[Link] = 9.54 ; [Link] = 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 */
22
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));
23
Segmentation Fault
24
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
a[0] a[1]
a: 1 2 3 4 5 6
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]
a[0][0] a[0][1] a[0][2]
• on se permet de le représenter a[0] 1 2 3
ainsi pour plus de lisibilité : a[1] 4 5 6
a[1][0] a[1][1] a[1][2]
25
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] )
a a[0][0] a[0][1] a[0][2]
a[0] 1 2 3
a[1] 4 5 6
a[1][0] a[1][1] a[1][2]
• Accès aux composantes :
a[1]+1
a[i][j] (à préférer J)
ou *( a[i] + j )
ou encore *( *(a+i) + j )
26
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
27
Le tableau de pointeurs !
s[2]
s[0]
s[1]
s O n \0
char s[] = "On"; /* idem. char s[3]= .. */
char s1[] = "est"; s1 e s t \0
char s2[] = "jeudi";
s2 j e u d i \0
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
s[2]
s[0]
s[1]
char * tabs[] = {s, s1, s2}; tabs
/* idem ….= { &s[0], &s1[0], &s2[0] } */ s O n \0
tabs[1][2] = x; s1 e s x \0
/* car idem *( *(tabs+1) + 2)=… */ s2 j e u d i \0
28
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; tabs
s[2]
s[0]
s[1]
L’accès (toujours pareil) : tabs s O n \0
ptr_s[1][2] = X; ptr_s s1 e s X \0
ou s2 j e u d i \0
*(*(ptr_s+1)+2) = X;
29
Tableau de pointeurs sur entiers !
mat
a[2]
a[0]
a[1]
int a[3], b[4], c[3];
int * mat[3] = {a,b,c};
( tab ) a 1 -4 0
ptr_m b 7 2 11 1
( int ** ptr_m = tab; ) c -4 6 9
Le tableau int* mat[3] peut être alloué dynamiquement puis remplit avec les noms
(adresses) des tableaux:
&bloc a 1 -4 0
ptr_m b 7 2 11 1
int **ptr_m;
c -4 6 9
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.
30
Tableaux multidimensionnels dynamiques
• Allocation dynamique : d’un tableau de dimension (3,4)
1. Allocation d’un tableau de 3 pointeurs sur des entiers
&bloc &?
int ** ptr_m; 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 &bloc’ ? ? ? ?
ptr_m &bloc’’ ? ? ? ?
&bloc’’’ ? ? ? ?
31
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** */
}
32
Tableaux multidimensionnels
• Autre solution: considérer le tableau 2D comme un vecteur
1 2 3 4
1ère ligne 2ème ligne
• 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)
33
Tableaux multidimensionnels
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int m_toto[6] = {11,12,13,21,22,23}; //matrice 2x3
printf("%i %i %i \n",
m_toto[0*3+0], m_toto[0*3+1], m_toto[0*3+2]);
printf("%i %i %i \n",
*(m_toto+1*3), *(m_toto+1*3+1), *(m_toto+1*3+2));
}
Sortie écran:
11 12 13
21 22 23