• Aucun résultat trouvé

4. Pointeurs et allocation dynamique

N/A
N/A
Protected

Academic year: 2022

Partager "4. Pointeurs et allocation dynamique"

Copied!
33
0
0

Texte intégral

(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);

}

•  Résutat :

> valeur de i = 7

> adresse de i = 0x03F0 (1008(10))

542 7 24

@ memoire:

0x03EC 0x03F0 0x03F4

i

(3)

• 

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

(4)

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

*

*

(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;

avant:

qi = pi ; /* qi pointe sur même objet que pi */

après:

&e qi

&f pi

&f qi

&f pi

(6)

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

(7)

Pointeurs

xkcd.com

(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*/

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

(9)

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

(10)

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!

(11)

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

(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 = (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 ?

(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[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

?

(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] ;

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;

(21)

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 */

(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

• 

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]

(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] )

• 

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

(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 !

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

(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;

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

(29)

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

( )

(30)

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

&?

&?

&?

? ? ? ?

(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

• 

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

(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

Références

Documents relatifs

Adapter la fonction de création pour créer une liste linéaire chaînée dans l'ordre FIFO à partir de ce fichier binaire.. Exercice 2 : Supposons que les données

Allocation d’un bloc mémoire de 20 octets pour stocker 5 int. Ecriture de l’adresse de la 1ère case du bloc

- Si on n’a plus besoin d’un bloc de mémoire réservé dynamiquement par malloc, alors on peut le libérer à l’aide de la fonction

- Le pointeur « ptr » contient l’adresse mémoire d’une zone mémoire capable de contenir une variable de type « int ».. - La définition d’un pointeur n’alloue en

Analyse et programmation 2 - Les pointeurs et l'allocation dynamique de mémoire 10{. Les opérateurs des tableaux s appliquent

43 Il importe que les grilles élaborées par l’enseignant soient exprimées dans un langage que les élèves comprennent, et que l’enseignant fournisse un exemple de travail

Pour libérer de la mémoire allouée dynamiquement en C++, on utilisera l’opérateur delete...

Il est impé ératif d ratif d’ ’appeler l appeler l’ ’op opé érateur rateur delete delete lorsqu lorsqu’ ’on n on n’ ’a plus besoin a plus besoin de l de l’ ’espace