Les pointeurs
Aziz SRAI
Cours MIP S3
Organisation de la mémoire
SE
Segment de code
Segment de données
Pile
Tas
Pour qu’un programme fonctionne il doit être placé en mémoire centrale afin que le processeur:
▫ transfert des informations de la mémoire centrale vers ses registres;
▫ réalise des opérations (+,-,/,*,<,…);
▫ range les résultats dans la mémoire centrale.
La mémoire centrale est divisée en zones ou segments de taille fixe ou variable et pouvant
contenir des informations.
Système d'exploitation
Code du programme
Variables statiques (globales)
Appel de fonctions
2
Variables dynamiques
Mémoire RAM
Organisation de la mémoire
• Chaque segment mémoire de taille N est constitué d'un sous ensemble
d‘Octets (mots) ayant des adresses consécutives
SE
Segment de code
Segment de données
Pile
Tas
…
123500
123501
123502
...
adresses
3
Mémoire RAM
Les variables en mémoire
• Lors de l’exécution d’un programme, le compilateur alloue à chaque
variable déclarée un bloc ou une zone mémoire proportionnelle à son type
 associe à chaque variable une adresse automatiquement  inconnue
du programmeur.
• Pour des raisons évidentes de lisibilité, on désigne souvent les variables
par leur identificateur (nom), et non par leur adresse.
• C'est le compilateur qui fait le lien entre l'identificateur d'une variable et
son adresse en mémoire grâce à une table d'allocation mémoire.
Exemple: char x=‘A’; short int y;
1200 1201
A
Adresse
Contenu
Identificateur
… 1199 … 5362 5363 5364 …
x y
4
Les adresses
• Pour retrouver l'emplacement mémoire d'une variable, il suffit donc de
connaître l'adresse de l'octet où elle est stockée (ou, s'il s'agit d'une
variable qui recouvre plusieurs octets contigus, l'adresse de son premier
octets).
• L'adresse d'un objet (variable simple ou composée) étant un numéro en
mémoire, il s'agit d'un entier long quelque soit le type de l'objet considéré.
• L'opérateur & permet d'accéder à l'adresse d'une variable.
• Exemple:
char x =‘A’;
Short int y;
▫ &x est 1201
▫ &y est 5362
• &x est une constante  on ne peut pas faire figurer &x à gauche
d'un opérateur d'affectation.
&x=20;
A
… 1199 1201 … 5362 5363 5364 …
x
y
5
Intérêt des pointeurs
Pour pouvoir manipuler les adresses, on doit recourir à un nouveau type
de donnée appelé pointeur
Un pointeur est variable dont la valeur (contenu) est égale
à l'adresse d'un autre variable
On déclare un pointeur par l'instruction:
type *nom_du_pointeur;
où type est le type de l'objet pointé et * est appelé opérateur
d'indirection.
N.B: Même si la valeur d'un pointeur est toujours un entier long, le type
d'un pointeur dépend du type de l'objet pointé.
6
Initialisation des pointeurs
Short int y=35
p = &y;
Avant de manipuler un pointeur, et notamment de lui appliquer l'opérateur d'indirection *,
il faut l'initialiser sinon, par défaut, sa valeur du est égale à une constante symbolique
notée NULL définie dans <stdio.h>. Cette constante vaut 0 par défaut.
short int *p;
7
85 86 85 86 87 88 89 … 119 120 121 122 …
0 35
p y
85 86 85 86 87 88
89
… 119 120 121 122 …
121 35
p y
1230 1231 … 2235 …
A A 1231
p1 i j p2
Exemple
On peut dans un programme manipuler à la fois les objets p et *p. Ces
deux manipulations sont très différentes.
Exemple:
main()
{
char i = ‘9’, j = ’A’; /*1*/
char *p1, *p2; /*2*/
p1 = &i; /*3*/
p2 = &j; /*4*/
*p1 = *p2;
p1 = p2;
/*5*/
/*6*/
}
Après l'affectation 5
objet adresse valeur
i 1230 A
j 1231 A
p1 116 1230
p2 2235 1231
Après l'affectation 6
objet adresse valeur
i 1230 A
j 1231 A
p1 116 1231
p2 2235 1231
0 9 12 0
p1
Après 3 et 4
i j p2
… 116 … 1230 1231 … 2235 …
… 116 … 1230 1231 … 2235 …
Après 1 et 2
1230 9 A 1231
p1
Après 5 et 6
… 116
…
1231
i j p2
8
Arithmétique des pointeurs
9
La valeur d'un pointeur étant un entier, les seules opérations arithmétiques valides sur
les pointeurs sont l'addition et la soustraction d'un entier ainsi que la soustraction de
deux pointeur.
L'addition et soustraction d'un entier:
Si i est un entier et p est un pointeur sur un objet de type type,
• l'expression p+i désigne un pointeur sur un objet de type type dont la valeur (adresse)
est:
p+i=p+i*sizeof(type);
• l'expression p-i désigne un pointeur sur un objet de type type dont la valeur (adresse)
est:
P-i=p-i*sizeof(type);
La soustraction de deux pointeurs:
• Si p et q deux pointeurs sur des objets de type type, l'expression p-q désigne un
entier dont la valeur (dresse) est égale à:
p-q=(p-q)/sizeof(type);
N.B: Les opérateurs de comparaison sont également applicables aux pointeurs, à condition de comparer des
pointeurs qui pointent vers des objets de même type.
Exemple
main()
{
long int i = 5;
long int *p1, *p2;
p1 = &i;
p2 = p1 + 1;
printf("p1 = %ld 
t p2 = %ld
n",p1,p2);
}
L'addition:
Si &i=1230, ce programme affichera: p1 =
1230 p2 =
1234
La soustraction:
10
main()
{
long int i = 5;
long int *p1, *p2;
p1 = &i;
p2 = p1 - 1;
printf("p1 = %ld 
t
}
p2 = %ldt p1-p2 = %dn",p1,p2,p1-p2);
Si &i=1230,
ce programme affichera: p1 = 1230 p2 = 1226 p1-p2=1
Allocation dynamique de la mémoire
L'allocation de la mémoire pour les variables du programme se fait automatiquement par
le compilateur du langage C. Le programmeur peut cependant intervenir dans le processus
d'allocation de la mémoire pour des variables pendant l'exécution du programme grâce à
la technique
d'Allocation dynamique par la fonction malloc()de la bibliothèque <stdlib.h>
malloc(nombre_octets)
• Cette fonction retourne un pointeur de type char* pointant vers le premier
octet d’une zone mémoire de taille nombre_octets octets.
• Pour pointer des variables de type pointeur vers des objets qui ne sont pas de
type char, il faut convertir le type de retour de la fonction malloc()à l'aide d'un
cast.
• L'argument nombre_octets est souvent donné à l'aide de la fonction sizeof() qui
renvoie le nombre d'octets utilisés pour stocker un objet.
11
Allocation dynamique de la mémoire
12
Pour allouer dynamiquement un espace mémoire pour un objet de type
int, on utilise un pointeur vers un entier, que l'on initialise de la manière
suivante:
#include <stdlib.h>
int *p;
p = (int*)malloc(sizeof(int));
Remarque:
On aurait pu écrire également
p = (int*)malloc(4);
puisqu'un objet de type int est stocké sur 4 octets.
Mais on préférera la première écriture qui a l'avantage d'être standard.
N.B: La fonction malloc()permet de réserver un espace de la mémoire de la
zone Tas proportionnel à son argument (paramètre).
Exemple 1
Soit le programme suivant:
#include <stdio.h>
#include <stdlib.h>
main()
{
int i = 5;
int *p;
printf("valeur de p avant allocation = %dn",p);
p = (int*)malloc(sizeof(int));
printf("valeur de p après allocation = %dn",p);
*p = i;
printf("Apres affectation *p = %dn",*p);
}
Ce programme définit un pointeur p sur un objet de type int, et affecte à *p
la valeur de la variable i. Si la zone pointé par p a l'adresse 3520, il
imprimera à l'écran :
• valeur de p avant allocation = 0
• valeur de p après allocation = 3520 (son contenu est la valeur se
trouvant à l'endroit réservé)
• Après affectation *p = 5
13
Exemple 2
La fonction malloc() permet également d'allouer un espace pour plusieurs
objets contigus en mémoire.
• On peut écrire par exemple:
#include <stdio.h>
#include <stdlib.h>
main()
{
short int i = 3;
short int j =
6; short int
*p;
p = (short
int*)malloc(2 *
sizeof(short
int));
*p = i;
*(p + 1) = j;
printf("p = %dt
*p = %dt p+1 =
%dt *(p+1) =
%d
n",p,*p,p+1,*(p+
1));
}
14
Fonction calloc()
15
• La fonction calloc() de la librairie <stdlib.h> a le
même rôle que la fonction malloc() mais elle initialise en
plus l'objet pointé *p à zéro.
• Sa syntaxe est:
calloc(nb-objets,taille-objets)
• Si p est de type int*, l'instruction
p = (int*)calloc(N,sizeof(int));
• est équivalente à
p = (int*)malloc(N * sizeof(int));
for (i = 0; i < N; i++)
*(p + i) = 0;
Dés-allocation mémoire
16
• lorsque l'on n'a plus besoin de l'espace-mémoire alloué dynamiquement
(c'est-à-dire quand on n'utilise plus la zone mémoire pointée par p), il faut
libérer cette place en mémoire. Ceci se fait à l'aide de l'instruction free qui
a pour syntaxe:
free(nom_du_pointeur);
• Dans ce cas nom_du_pointeur pointera toujours au même endroit de
la mémoire, mais on ne pourra plus utiliser le bloc pointé pour stocker
des objets.
• A toute instruction de type malloc() ou calloc() doit correspondre
une instruction free(). Sinon, l'ordinateur le fait à votre place mais un
bon programmeur ne doit pas lui faire confiance !
Pointeurs et tableaux à une dimension
• Tout tableau en C est en fait un pointeur constant.
• Dans la déclaration int t[10]; t est un pointeur constant (non modifiable)
dont la valeur est l'adresse du premier élément du tableau. Autrement dit,
t a pour valeur &t[0].
• On peut donc utiliser un pointeur initialisé à t pour parcourir les éléments du
tableau.
#define N 5
int t[5] = {1, 2, 6, 0, 7};
main()
{
int i;
int *p;
p =
t;
for (i
= 0; i
< N; i+
+)
{
pri
ntf
("
%d

17
Apport des pointeurs pour les tableaux
• la manipulation de tableaux, et non de pointeurs, possède certains inconvénients dû
au fait qu'un tableau est un pointeur constant. Ainsi:
• on ne peut pas créer de tableaux dont la taille est une variable du programme;
• on ne peut pas créer de tableaux bidimensionnels dont les lignes n'ont pas toutes le
même nombre d'éléments.
• Ces opérations deviennent possibles dès que l'on manipule des pointeurs alloués
dynamiquement. Ainsi, pour créer un tableau dynamique de n éléments entiers
où n est une variable du programme, on écrit:
#include <stdlib.h>
main()
{
int n;
int *tab;
...
tab =
(int*)mal
loc(n *
sizeof(in
t));
//on peut
utiliser
calloc en
cas
d'initial
18
Exemple
#include <stdio.h>
#include <stdlib.h>
main()
{
int i;
int tab[10] ;
for(i=0; i<10; i++)
tab[i]=i;
for(i=0; i<10; i++)
printf("%d
n",tab[i]);
}
#include <stdio.h>
#include <stdlib.h>
main()
{
int i, nb;
int* p;
19
printf("entrer le nombre nb: ");
scanf("%d",&nb);
p=(int*)malloc(nb*sizeof(int));
for(i=0; i<nb; i++)
*(p+i)=i;
for(i=0; i<nb; i++)
printf("%
dn",*(p+i));
free(p);
}
Pointeurs et tableaux à plusieurs dimensions
• Un tableau à deux dimensions est, par définition, un tableau de tableaux. Il s'agit
donc en fait d'un pointeur vers un pointeur.
• Considérons le tableau à deux dimensions défini par :
int t[M][N];
▫ t est un pointeur, qui pointe vers un objet qui est lui-même de type pointeur
d'entier;
▫ t a une valeur constante égale à l'adresse du premier élément du tableau, &t[0]
[0];
▫ De même t[i], pour i allant de 0 et M-1, est un pointeur constant vers un objet
de type pointeur vers un entier, qui est le premier élément de la ligne d'indice i;
▫ t[i] a donc une valeur constante qui est égale à &t[i][0]
j 0 1 … N-1 0 1 … N-1 0 1 ... N-1
0 1 … M-1
i
20
Apport des pointeurs pour les tableaux à plusieurs
dimensions
• Comme pour les tableaux à deux dimension, on peut créer des
tableaux dynamiques à plusieurs dimensions.
N
colonnes
• Pour créer avec un pointeur de pointeur ou une matrice à M lignes
et à coefficients entiers, on écrit :
main()
{
int M, N;
int **tab;
21
tab = (int**)malloc(M * sizeof(int*));
for (i = 0; i < M; i++)
tab[i] = (int*)malloc(N *
sizeof(int));
....
for (i = 0; i < M; i++)
free(tab[i]);
free(tab);
}
Une structure est un structure de donnes qui rassemble sous un même nom des variables
pouvant être de types différents. Chaque élément de la structure (appelé champ) est
désigné par un nom qui permet d'y avoir accès.
La définition d'une structure en C suit la syntaxe:
struct Nom_Structure
{
type_1 Champ_1;
type_2
Champ_2;
type_3 Champ_3;
...
}; //La dernière
accolade doit
être suivie d'un
point-virgule !
Exemple
Rappels sur les structures (enregistrements)
//définition de la structure d’un nombre complexe
struct NbrComplexe
{
double reel;
double
imag;
};
22
Déclaration d'une variable structurée
• La déclaration d'une variable structurée est une opération qui consiste à créer une
variable ayant comme type celui d'une structure que l'on a précédemment défini.
• La définition d'une variable structurée se fait de deux manières:
struct Nom_Structure V1, V2, …;
• On peut initialiser une variable de type structure dès sa définition en lui affectant
une liste de valeurs séparées par des virgules et entourées par des accolades.
• Il est possible d'affecter une variable de type structure dans une autre variable
de même type. Le contenu de chacun des champs de la première variable sera
alors recopié dans le champ correspondant de la seconde variable.
Exemple:
Struct NbrComplexe x = {1,0}; //reel de x vaut 1 et imag de x vaut 0
struct
y = x;
NbrComplexe y;
//reel de y vaut 1 et imag de x vaut 0
23
Accès aux champs d'une structure
• Pour accéder aux champs d'une structure on utilise l'opérateur de champ
(un simple point) placé entre le nom de la variable structurée que l'on a
défini et le nom du champ :
Nom_Variable.Nom_Champ;
Exemple: pour affecter des valeurs à la variable y (variable de type
complexe
définie précédemment), on écrit:
Struct NbrComplexe y;
y.imag = 1;
y.reel = 2;
//y={2,1}
24
Struct personne
{
char nom[30];
char prenom[20];
float heures[31];
};
Struct personne employe;
25
• employe.heures[4] désigne le 5ème élément du tableau heures de la
structure employe (de type float).
• employe.nom[0] désigne le premier caractère du champ nom de la
structure employe.
Structures comportant des tableaux
En C, il est possible de définir des tableaux dont les éléments sont de type
structure:
26
Struct point {
char nom;
int x;
int y;
};
Struct point
courbe[50];
La structure point représente par exemple un point d’un plan défini par son
nom et ses coordonnées x et y.
Tableaux de structures
Tableaux de structures
27
L’instruction: Struct point courbe[50]; indique que la
structure courbe est un ensemble de 50 points de type structure
précédemment définie.
- courbe[i].nom représente le nom du point i du tableau courbe
(de type char).
- courbe[i].x représente la valeur du champ x du point i du tableau
courbe.
- courbe[4] représente le 5ème élément du tableau courbe.
Structures comportant des structures
28
Supposons à l’intérieur de la structure personne déjà définie,
nous avons besoin d’introduire une date: la date d’embauche.
Cette date est une structure comportant 3 champs (jour, mois,
année).
Struct date
{
int jour;
int mois;
int annee;
};
Structures comportant des structures
29
La déclaration de la structure personne sera ainsi présentée:
struct personne
{
char nom[30];
char prenom[20];
float heures[31];
struct date
date_embauche;
};
struct personne employe;
employe.date_embauche.annee représente l’année d’embauche
correspondant à la structure employe (type int).
Pointeurs et structures
• Les objets de type structure en C possèdent une adresse, correspondant à l'adresse
du premier champ de la structure. On peut donc manipuler des pointeurs sur des
structures.
• Si p est un pointeur sur une structure, on peut accéder à la valeur d’un champ de la
structure pointé par l'expression
(*p).champ
• Il est obligatoire de mettre des parenthèses autour de *p car l'opérateur . est
plus prioritaire que l'opérateur *
• Cette notation peut être simplifiée grâce à l'opérateur pointeur de membre
de structure, noté ->. L'expression précédente est strictement équivalente à
p->champ
Exemple:
complexe x = {1.5,-1.25};
complexe *p = &x;
p->reel = 1;
p->imag = -1;
/* x vaut {1,-1} */
complexe x = {1.5,-1.25};
complexe *p = &x;
(*p).reel = 1;
(*p).imag = -1;
/* x vaut {1,-1} */
30
Pointeurs et fonctions
31
Définition:
Une fonction désigne un ensemble de données et d'instructions qui fournissent
une solution à une (petite) partie bien définie d'un problème complexe.
• Une fonction peut faire appel à d'autres fonctions, leur transmettre des
données et recevoir des données en retour.
• L'ensemble des fonctions ainsi reliés doit alors être capable de résoudre le
problème global.
Définition en langage C:
Type_retour Nom_fonction(type1 parmetre1, type2 parametre2,…)
• Un paramètre formel est un nom de variable utilisé lors de la définition de la
fonction.
• Un paramètre réel ou effectif, est une valeur ou une variable utilisé comme
paramètre lors de l’appel de la fonction.
Passage des paramètres aux fonctions
32
Les paramètres d'une fonction sont consis comme des variables locales qui
sont initialisées automatiquement par les valeurs utilisées lors d'un appel.
Passage des paramètres par valeurs:
• Si l'expression passée en paramètre est une variable, son contenu est copié dans
la variable locale.
• Aucune modification de la variable locale dans la fonction appelée ne modifie la
variable passée en paramètre, parce que ces modifications ne s'appliquent qu'à une
copie de cette dernière.
Passage des paramètres par adresses:
• Consiste à passer non plus la valeur des variables comme paramètre, mais à passer
les variables elles-mêmes (par utilisation de pointeurs)  Toute modification des
paramètres de la fonction appelée entraîne la modification des variables réelles
passées en paramètre.
Exemple de passage par valeurs
#include <stdio.h>
void Change(int a,int b)
{
if (b>a)
a=b;
}
main()
{
int c=7;
Change(c,9);
printf(" c=%d
", c);
}
Paramètre formel
a est initialisé
avec la valeur
du paramètre
effectif (réel)
33
Exemple de passage par adresses
#include <stdio.h>
void Change(int *a,int b)
{
if (b>*a)
*a=b;
}
m
a
i
n
(
)
{
int c=7;
Change(&c,9);
printf(" c=%d
", c);
}
34
Conclusion
35
Les pointeurs constituent une notion essentielle du langage C. Il faut
prendre le temps de bien comprendre comment ils fonctionnent car
beaucoup d'autres notions sont basées dessus.
• Chaque variable est stockée à une adresse précise en mémoire.
• Les pointeurs sont semblables aux variables, sauf qu'au lieu de stocker
un nombre ils stockent l'adresse à laquelle se trouve une autre variable en
mémoire.
• Si on place un symbole & devant un nom de variable, on obtient
son adresse au lieu de sa valeur.
• Si on place un symbole * devant un nom de pointeur, on obtient la
valeur de la variable stockée dans l'adresse pointée.

Langage_C_Les_pointeurs_en_langage_C.pptx

  • 1.
  • 2.
    Organisation de lamémoire SE Segment de code Segment de données Pile Tas Pour qu’un programme fonctionne il doit être placé en mémoire centrale afin que le processeur: ▫ transfert des informations de la mémoire centrale vers ses registres; ▫ réalise des opérations (+,-,/,*,<,…); ▫ range les résultats dans la mémoire centrale. La mémoire centrale est divisée en zones ou segments de taille fixe ou variable et pouvant contenir des informations. Système d'exploitation Code du programme Variables statiques (globales) Appel de fonctions 2 Variables dynamiques Mémoire RAM
  • 3.
    Organisation de lamémoire • Chaque segment mémoire de taille N est constitué d'un sous ensemble d‘Octets (mots) ayant des adresses consécutives SE Segment de code Segment de données Pile Tas … 123500 123501 123502 ... adresses 3 Mémoire RAM
  • 4.
    Les variables enmémoire • Lors de l’exécution d’un programme, le compilateur alloue à chaque variable déclarée un bloc ou une zone mémoire proportionnelle à son type  associe à chaque variable une adresse automatiquement  inconnue du programmeur. • Pour des raisons évidentes de lisibilité, on désigne souvent les variables par leur identificateur (nom), et non par leur adresse. • C'est le compilateur qui fait le lien entre l'identificateur d'une variable et son adresse en mémoire grâce à une table d'allocation mémoire. Exemple: char x=‘A’; short int y; 1200 1201 A Adresse Contenu Identificateur … 1199 … 5362 5363 5364 … x y 4
  • 5.
    Les adresses • Pourretrouver l'emplacement mémoire d'une variable, il suffit donc de connaître l'adresse de l'octet où elle est stockée (ou, s'il s'agit d'une variable qui recouvre plusieurs octets contigus, l'adresse de son premier octets). • L'adresse d'un objet (variable simple ou composée) étant un numéro en mémoire, il s'agit d'un entier long quelque soit le type de l'objet considéré. • L'opérateur & permet d'accéder à l'adresse d'une variable. • Exemple: char x =‘A’; Short int y; ▫ &x est 1201 ▫ &y est 5362 • &x est une constante  on ne peut pas faire figurer &x à gauche d'un opérateur d'affectation. &x=20; A … 1199 1201 … 5362 5363 5364 … x y 5
  • 6.
    Intérêt des pointeurs Pourpouvoir manipuler les adresses, on doit recourir à un nouveau type de donnée appelé pointeur Un pointeur est variable dont la valeur (contenu) est égale à l'adresse d'un autre variable On déclare un pointeur par l'instruction: type *nom_du_pointeur; où type est le type de l'objet pointé et * est appelé opérateur d'indirection. N.B: Même si la valeur d'un pointeur est toujours un entier long, le type d'un pointeur dépend du type de l'objet pointé. 6
  • 7.
    Initialisation des pointeurs Shortint y=35 p = &y; Avant de manipuler un pointeur, et notamment de lui appliquer l'opérateur d'indirection *, il faut l'initialiser sinon, par défaut, sa valeur du est égale à une constante symbolique notée NULL définie dans <stdio.h>. Cette constante vaut 0 par défaut. short int *p; 7 85 86 85 86 87 88 89 … 119 120 121 122 … 0 35 p y 85 86 85 86 87 88 89 … 119 120 121 122 … 121 35 p y
  • 8.
    1230 1231 …2235 … A A 1231 p1 i j p2 Exemple On peut dans un programme manipuler à la fois les objets p et *p. Ces deux manipulations sont très différentes. Exemple: main() { char i = ‘9’, j = ’A’; /*1*/ char *p1, *p2; /*2*/ p1 = &i; /*3*/ p2 = &j; /*4*/ *p1 = *p2; p1 = p2; /*5*/ /*6*/ } Après l'affectation 5 objet adresse valeur i 1230 A j 1231 A p1 116 1230 p2 2235 1231 Après l'affectation 6 objet adresse valeur i 1230 A j 1231 A p1 116 1231 p2 2235 1231 0 9 12 0 p1 Après 3 et 4 i j p2 … 116 … 1230 1231 … 2235 … … 116 … 1230 1231 … 2235 … Après 1 et 2 1230 9 A 1231 p1 Après 5 et 6 … 116 … 1231 i j p2 8
  • 9.
    Arithmétique des pointeurs 9 Lavaleur d'un pointeur étant un entier, les seules opérations arithmétiques valides sur les pointeurs sont l'addition et la soustraction d'un entier ainsi que la soustraction de deux pointeur. L'addition et soustraction d'un entier: Si i est un entier et p est un pointeur sur un objet de type type, • l'expression p+i désigne un pointeur sur un objet de type type dont la valeur (adresse) est: p+i=p+i*sizeof(type); • l'expression p-i désigne un pointeur sur un objet de type type dont la valeur (adresse) est: P-i=p-i*sizeof(type); La soustraction de deux pointeurs: • Si p et q deux pointeurs sur des objets de type type, l'expression p-q désigne un entier dont la valeur (dresse) est égale à: p-q=(p-q)/sizeof(type); N.B: Les opérateurs de comparaison sont également applicables aux pointeurs, à condition de comparer des pointeurs qui pointent vers des objets de même type.
  • 10.
    Exemple main() { long int i= 5; long int *p1, *p2; p1 = &i; p2 = p1 + 1; printf("p1 = %ld t p2 = %ld n",p1,p2); } L'addition: Si &i=1230, ce programme affichera: p1 = 1230 p2 = 1234 La soustraction: 10 main() { long int i = 5; long int *p1, *p2; p1 = &i; p2 = p1 - 1; printf("p1 = %ld t } p2 = %ldt p1-p2 = %dn",p1,p2,p1-p2); Si &i=1230, ce programme affichera: p1 = 1230 p2 = 1226 p1-p2=1
  • 11.
    Allocation dynamique dela mémoire L'allocation de la mémoire pour les variables du programme se fait automatiquement par le compilateur du langage C. Le programmeur peut cependant intervenir dans le processus d'allocation de la mémoire pour des variables pendant l'exécution du programme grâce à la technique d'Allocation dynamique par la fonction malloc()de la bibliothèque <stdlib.h> malloc(nombre_octets) • Cette fonction retourne un pointeur de type char* pointant vers le premier octet d’une zone mémoire de taille nombre_octets octets. • Pour pointer des variables de type pointeur vers des objets qui ne sont pas de type char, il faut convertir le type de retour de la fonction malloc()à l'aide d'un cast. • L'argument nombre_octets est souvent donné à l'aide de la fonction sizeof() qui renvoie le nombre d'octets utilisés pour stocker un objet. 11
  • 12.
    Allocation dynamique dela mémoire 12 Pour allouer dynamiquement un espace mémoire pour un objet de type int, on utilise un pointeur vers un entier, que l'on initialise de la manière suivante: #include <stdlib.h> int *p; p = (int*)malloc(sizeof(int)); Remarque: On aurait pu écrire également p = (int*)malloc(4); puisqu'un objet de type int est stocké sur 4 octets. Mais on préférera la première écriture qui a l'avantage d'être standard. N.B: La fonction malloc()permet de réserver un espace de la mémoire de la zone Tas proportionnel à son argument (paramètre).
  • 13.
    Exemple 1 Soit leprogramme suivant: #include <stdio.h> #include <stdlib.h> main() { int i = 5; int *p; printf("valeur de p avant allocation = %dn",p); p = (int*)malloc(sizeof(int)); printf("valeur de p après allocation = %dn",p); *p = i; printf("Apres affectation *p = %dn",*p); } Ce programme définit un pointeur p sur un objet de type int, et affecte à *p la valeur de la variable i. Si la zone pointé par p a l'adresse 3520, il imprimera à l'écran : • valeur de p avant allocation = 0 • valeur de p après allocation = 3520 (son contenu est la valeur se trouvant à l'endroit réservé) • Après affectation *p = 5 13
  • 14.
    Exemple 2 La fonctionmalloc() permet également d'allouer un espace pour plusieurs objets contigus en mémoire. • On peut écrire par exemple: #include <stdio.h> #include <stdlib.h> main() { short int i = 3; short int j = 6; short int *p; p = (short int*)malloc(2 * sizeof(short int)); *p = i; *(p + 1) = j; printf("p = %dt *p = %dt p+1 = %dt *(p+1) = %d n",p,*p,p+1,*(p+ 1)); } 14
  • 15.
    Fonction calloc() 15 • Lafonction calloc() de la librairie <stdlib.h> a le même rôle que la fonction malloc() mais elle initialise en plus l'objet pointé *p à zéro. • Sa syntaxe est: calloc(nb-objets,taille-objets) • Si p est de type int*, l'instruction p = (int*)calloc(N,sizeof(int)); • est équivalente à p = (int*)malloc(N * sizeof(int)); for (i = 0; i < N; i++) *(p + i) = 0;
  • 16.
    Dés-allocation mémoire 16 • lorsquel'on n'a plus besoin de l'espace-mémoire alloué dynamiquement (c'est-à-dire quand on n'utilise plus la zone mémoire pointée par p), il faut libérer cette place en mémoire. Ceci se fait à l'aide de l'instruction free qui a pour syntaxe: free(nom_du_pointeur); • Dans ce cas nom_du_pointeur pointera toujours au même endroit de la mémoire, mais on ne pourra plus utiliser le bloc pointé pour stocker des objets. • A toute instruction de type malloc() ou calloc() doit correspondre une instruction free(). Sinon, l'ordinateur le fait à votre place mais un bon programmeur ne doit pas lui faire confiance !
  • 17.
    Pointeurs et tableauxà une dimension • Tout tableau en C est en fait un pointeur constant. • Dans la déclaration int t[10]; t est un pointeur constant (non modifiable) dont la valeur est l'adresse du premier élément du tableau. Autrement dit, t a pour valeur &t[0]. • On peut donc utiliser un pointeur initialisé à t pour parcourir les éléments du tableau. #define N 5 int t[5] = {1, 2, 6, 0, 7}; main() { int i; int *p; p = t; for (i = 0; i < N; i+ +) { pri ntf (" %d 17
  • 18.
    Apport des pointeurspour les tableaux • la manipulation de tableaux, et non de pointeurs, possède certains inconvénients dû au fait qu'un tableau est un pointeur constant. Ainsi: • on ne peut pas créer de tableaux dont la taille est une variable du programme; • on ne peut pas créer de tableaux bidimensionnels dont les lignes n'ont pas toutes le même nombre d'éléments. • Ces opérations deviennent possibles dès que l'on manipule des pointeurs alloués dynamiquement. Ainsi, pour créer un tableau dynamique de n éléments entiers où n est une variable du programme, on écrit: #include <stdlib.h> main() { int n; int *tab; ... tab = (int*)mal loc(n * sizeof(in t)); //on peut utiliser calloc en cas d'initial 18
  • 19.
    Exemple #include <stdio.h> #include <stdlib.h> main() { inti; int tab[10] ; for(i=0; i<10; i++) tab[i]=i; for(i=0; i<10; i++) printf("%d n",tab[i]); } #include <stdio.h> #include <stdlib.h> main() { int i, nb; int* p; 19 printf("entrer le nombre nb: "); scanf("%d",&nb); p=(int*)malloc(nb*sizeof(int)); for(i=0; i<nb; i++) *(p+i)=i; for(i=0; i<nb; i++) printf("% dn",*(p+i)); free(p); }
  • 20.
    Pointeurs et tableauxà plusieurs dimensions • Un tableau à deux dimensions est, par définition, un tableau de tableaux. Il s'agit donc en fait d'un pointeur vers un pointeur. • Considérons le tableau à deux dimensions défini par : int t[M][N]; ▫ t est un pointeur, qui pointe vers un objet qui est lui-même de type pointeur d'entier; ▫ t a une valeur constante égale à l'adresse du premier élément du tableau, &t[0] [0]; ▫ De même t[i], pour i allant de 0 et M-1, est un pointeur constant vers un objet de type pointeur vers un entier, qui est le premier élément de la ligne d'indice i; ▫ t[i] a donc une valeur constante qui est égale à &t[i][0] j 0 1 … N-1 0 1 … N-1 0 1 ... N-1 0 1 … M-1 i 20
  • 21.
    Apport des pointeurspour les tableaux à plusieurs dimensions • Comme pour les tableaux à deux dimension, on peut créer des tableaux dynamiques à plusieurs dimensions. N colonnes • Pour créer avec un pointeur de pointeur ou une matrice à M lignes et à coefficients entiers, on écrit : main() { int M, N; int **tab; 21 tab = (int**)malloc(M * sizeof(int*)); for (i = 0; i < M; i++) tab[i] = (int*)malloc(N * sizeof(int)); .... for (i = 0; i < M; i++) free(tab[i]); free(tab); }
  • 22.
    Une structure estun structure de donnes qui rassemble sous un même nom des variables pouvant être de types différents. Chaque élément de la structure (appelé champ) est désigné par un nom qui permet d'y avoir accès. La définition d'une structure en C suit la syntaxe: struct Nom_Structure { type_1 Champ_1; type_2 Champ_2; type_3 Champ_3; ... }; //La dernière accolade doit être suivie d'un point-virgule ! Exemple Rappels sur les structures (enregistrements) //définition de la structure d’un nombre complexe struct NbrComplexe { double reel; double imag; }; 22
  • 23.
    Déclaration d'une variablestructurée • La déclaration d'une variable structurée est une opération qui consiste à créer une variable ayant comme type celui d'une structure que l'on a précédemment défini. • La définition d'une variable structurée se fait de deux manières: struct Nom_Structure V1, V2, …; • On peut initialiser une variable de type structure dès sa définition en lui affectant une liste de valeurs séparées par des virgules et entourées par des accolades. • Il est possible d'affecter une variable de type structure dans une autre variable de même type. Le contenu de chacun des champs de la première variable sera alors recopié dans le champ correspondant de la seconde variable. Exemple: Struct NbrComplexe x = {1,0}; //reel de x vaut 1 et imag de x vaut 0 struct y = x; NbrComplexe y; //reel de y vaut 1 et imag de x vaut 0 23
  • 24.
    Accès aux champsd'une structure • Pour accéder aux champs d'une structure on utilise l'opérateur de champ (un simple point) placé entre le nom de la variable structurée que l'on a défini et le nom du champ : Nom_Variable.Nom_Champ; Exemple: pour affecter des valeurs à la variable y (variable de type complexe définie précédemment), on écrit: Struct NbrComplexe y; y.imag = 1; y.reel = 2; //y={2,1} 24
  • 25.
    Struct personne { char nom[30]; charprenom[20]; float heures[31]; }; Struct personne employe; 25 • employe.heures[4] désigne le 5ème élément du tableau heures de la structure employe (de type float). • employe.nom[0] désigne le premier caractère du champ nom de la structure employe. Structures comportant des tableaux
  • 26.
    En C, ilest possible de définir des tableaux dont les éléments sont de type structure: 26 Struct point { char nom; int x; int y; }; Struct point courbe[50]; La structure point représente par exemple un point d’un plan défini par son nom et ses coordonnées x et y. Tableaux de structures
  • 27.
    Tableaux de structures 27 L’instruction:Struct point courbe[50]; indique que la structure courbe est un ensemble de 50 points de type structure précédemment définie. - courbe[i].nom représente le nom du point i du tableau courbe (de type char). - courbe[i].x représente la valeur du champ x du point i du tableau courbe. - courbe[4] représente le 5ème élément du tableau courbe.
  • 28.
    Structures comportant desstructures 28 Supposons à l’intérieur de la structure personne déjà définie, nous avons besoin d’introduire une date: la date d’embauche. Cette date est une structure comportant 3 champs (jour, mois, année). Struct date { int jour; int mois; int annee; };
  • 29.
    Structures comportant desstructures 29 La déclaration de la structure personne sera ainsi présentée: struct personne { char nom[30]; char prenom[20]; float heures[31]; struct date date_embauche; }; struct personne employe; employe.date_embauche.annee représente l’année d’embauche correspondant à la structure employe (type int).
  • 30.
    Pointeurs et structures •Les objets de type structure en C possèdent une adresse, correspondant à l'adresse du premier champ de la structure. On peut donc manipuler des pointeurs sur des structures. • Si p est un pointeur sur une structure, on peut accéder à la valeur d’un champ de la structure pointé par l'expression (*p).champ • Il est obligatoire de mettre des parenthèses autour de *p car l'opérateur . est plus prioritaire que l'opérateur * • Cette notation peut être simplifiée grâce à l'opérateur pointeur de membre de structure, noté ->. L'expression précédente est strictement équivalente à p->champ Exemple: complexe x = {1.5,-1.25}; complexe *p = &x; p->reel = 1; p->imag = -1; /* x vaut {1,-1} */ complexe x = {1.5,-1.25}; complexe *p = &x; (*p).reel = 1; (*p).imag = -1; /* x vaut {1,-1} */ 30
  • 31.
    Pointeurs et fonctions 31 Définition: Unefonction désigne un ensemble de données et d'instructions qui fournissent une solution à une (petite) partie bien définie d'un problème complexe. • Une fonction peut faire appel à d'autres fonctions, leur transmettre des données et recevoir des données en retour. • L'ensemble des fonctions ainsi reliés doit alors être capable de résoudre le problème global. Définition en langage C: Type_retour Nom_fonction(type1 parmetre1, type2 parametre2,…) • Un paramètre formel est un nom de variable utilisé lors de la définition de la fonction. • Un paramètre réel ou effectif, est une valeur ou une variable utilisé comme paramètre lors de l’appel de la fonction.
  • 32.
    Passage des paramètresaux fonctions 32 Les paramètres d'une fonction sont consis comme des variables locales qui sont initialisées automatiquement par les valeurs utilisées lors d'un appel. Passage des paramètres par valeurs: • Si l'expression passée en paramètre est une variable, son contenu est copié dans la variable locale. • Aucune modification de la variable locale dans la fonction appelée ne modifie la variable passée en paramètre, parce que ces modifications ne s'appliquent qu'à une copie de cette dernière. Passage des paramètres par adresses: • Consiste à passer non plus la valeur des variables comme paramètre, mais à passer les variables elles-mêmes (par utilisation de pointeurs)  Toute modification des paramètres de la fonction appelée entraîne la modification des variables réelles passées en paramètre.
  • 33.
    Exemple de passagepar valeurs #include <stdio.h> void Change(int a,int b) { if (b>a) a=b; } main() { int c=7; Change(c,9); printf(" c=%d ", c); } Paramètre formel a est initialisé avec la valeur du paramètre effectif (réel) 33
  • 34.
    Exemple de passagepar adresses #include <stdio.h> void Change(int *a,int b) { if (b>*a) *a=b; } m a i n ( ) { int c=7; Change(&c,9); printf(" c=%d ", c); } 34
  • 35.
    Conclusion 35 Les pointeurs constituentune notion essentielle du langage C. Il faut prendre le temps de bien comprendre comment ils fonctionnent car beaucoup d'autres notions sont basées dessus. • Chaque variable est stockée à une adresse précise en mémoire. • Les pointeurs sont semblables aux variables, sauf qu'au lieu de stocker un nombre ils stockent l'adresse à laquelle se trouve une autre variable en mémoire. • Si on place un symbole & devant un nom de variable, on obtient son adresse au lieu de sa valeur. • Si on place un symbole * devant un nom de pointeur, on obtient la valeur de la variable stockée dans l'adresse pointée.