Sunteți pe pagina 1din 144

PARTIE 2

Rappels et compléments du
langage C

1
Rappels

Un programme C est une fonction de nom main()


qui prend des arguments textuels en paramètre et
renvoie un code de retour d’erreur entier
Quelques possibilités
◮ Une déclaration de variable par ligne
◮ Une instruction élémentaire (pas bloc) par ligne
◮ Structure de contrôle : début de ligne
◮ Accolade ouvrante : fin de ligne
◮ Accolade fermante : seule sur une ligne
◮ Étiquette : seule sur une ligne

2
Types d’objet
◮ Types scalaires : objets élémentaires proches du
matériel
Types arithmétiques : pour faire des calculs au sens
classique
Pointeurs : référence à d’autres objets ou fonctions
◮ Types structurés
Vecteurs : répétition ordonnée d’un même type
Structures : rassemble éléments hétérogènes
Union : fait coexister plusieurs types au même
endroit mémoire
• Types de fonction : précise à la fois type valeur de
retour et type
arguments
• Types incomplets
◮ Incomplétude totale void
◮ Vecteur de taille non précisée

3
LES STRUCTURES
Le langage C permet de définir des modèles de structures
comme les autres langages évolués.
struct art // déclaration de structure
{ int numero;
int qte;
double prix;
};

struct art art1; art2;// variables

Pour simplifier, on peut nommer notre type article :

Typedef struct
{ int numero;
int qte;
double prix;
}article;

article art1; art2;// variables

4
Opérations sur structures

 Les opérations permises sur une structure


sont l’affectation (en considérant la
structure comme un tout), la récupération
de son adresse (opérateur &) et l’accès à
ses membres.
 On peut initialiser une structure au
moment de sa déclaration, par exemple :
article art1 ={15,50,12.5}
 Quelques opérations :
art1.numero = 15;
printf ("%e", art1 .prix);
art2 = art1;

5
Unions

Les unions permettent l’utilisation d’un


même espace mémoire par des données de
types différents à des moments différents.
12.1 Définition
La définition d’une union respecte une
syntaxe proche de celle d’une structure
union nom_de_union {
type1 nom_champ1 ;
type2 nom_champ2 ;
type3 nom_champ3 ;
type4 nom_champ4 ;
...
typeN nom_champ_N ;
} variables ;

6
Exemple

Dans ce qui suit; on définit deux


variables z1 et z2 construites sur le
modèle d’une zone qui peut contenir soit
un entier, soit un entier long, soit un
nombre avec point décimal, soit un
nombre avec point décimal long.
union zone {
int entier;
long entlong;
float flottant;
double floatlong;
} z1,z2;

7
Exemple

Lorsque l’on définit une variable


correspondant à un type union, le
compilateur réserve l’espace
mémoire nécessaire pour stocker le
plus grand des champs appartenant à
l’union. Dans notre exemple, le
compilateur réserve l’espace
mémoire nécessaire pour stocker un
double pour chacune des variables z1
et z2.

8
Accès aux champs

La syntaxe d’accès aux champs d’une union est


identique à celle pour accéder aux champs d’une
structure.
Une union ne contient cependant qu’une donnée à la
fois et l’accès à un champ de l’union pour obtenir une
valeur, doit être fait dans le type qui a été utilisé
pour stocker la valeur.
Les unions ont étés inventées pour utiliser un même
espace mémoire avec des types de données
différents dans des étapes différentes d’un même
programme.
Elles sont, par exemple, utilisées dans les
compilateurs.
Les différents “champs” d’une union sont à la même
adresse physique. Ainsi, les égalités suivantes sont
vraies :
&z1.entier == (int*)&z1.entlong
&z1.entier == (int*)&z1.flottant
&z1.entier == (int*)&z1.flotlong

9
Les classes d’allocation
de variables

10
Déclaration et définition

Une déclaration : une association de type avec un nom de


variable ou de fonction (dans ce cas la déclaration contient
aussi le type des arguments de la fonction, les noms des
arguments peuvent être omis),
Une définition de variable est l’association d’un identifiant à
un type et la spécification d’une classe mémoire.
Une définition c’est une déclaration et si c’est une variable,
une demande d’allocation d’espace pour cette variable, si
c’est une fonction la définition du corps de fonction contenant
les instructions associées à cette fonction.
De manière simpliste, une déclaration fait référence à une
définition dans une autre partie du programme.
Elle peut se traduire par “je sais qu’il existe une variable
ayant ce type et portant ce nom”.

11
Variables et déclarations

Les déclarations des variables peuvent


être :
 En dehors de toute fonction, il s’agit
alors de variables globales
 À l’intérieur d’un bloc, il s’agit de
variables locales
 Dans l’entête d’une fonction, il s’agit
alors de paramètres ou d’arguments
formels
 soit dans les parenthèses (fonction
définie avec un prototype)
 soit entre le nom de la fonction et
la première accolade (sans prototype)

12
Règle fondamentale de visibité

Toute variable ou fonction doit être


déclarée avant d’être utilisée.
(Par convention, dans le cas où une
fonction n’est pas connue, le compilateur
considère qu’elle retourne une valeur de
type int et il essaye d’inférer le type des
paramètres à partir de l’appel (les appels
postérieurs devront se conformer à ce
premier appel).
La fonction peut aussi être définie plus
loin dans le fichier.)

13
Exemple
int a; /*variable globale*/
void f1(){
long a; ; /*variable locale à f1*/
a = 50; /*modification locale à f1*/
}
void f2(void){
a= 10; /*modification globale*/
}
void f3(float a ){/*paramètre de f3*/
a = 10; /*modification paramètre local à f3*/
}
les règles de visibilité de noms nous permettent de dire
:
1. la fonction f3 peut appeler les fonctions f3, f2, et f1 ;
2. la fonction f2 peut appeler la fonction f2, et f1 ;
3. la fonction f1 ne peut que s’appeler elle-même ;

14
Les classes d’allocation
mémoire

La classe mémoire sert à expliciter la


visibilité d’une variable et son implantation en
machine.
Les classes mémoire sont :
global : durée de vie = celle du programme
auto : dans la pile
static : local au fichier ou garde une vie
après la mort
extern : définie ailleurs
register : essaye de garder en registre
(très rapide mais rare, donc chère...)

15
Les classes d’allocation
mémoire
Les classes mémoire sont :
global cette classe est celle des variables définies en
dehors d’une fonction. Ces variables sont accessibles à
toutes les fonctions. La durée de vie des variables de type
global est la même que celle du programme en cours
d’exécution.
local ou auto : cette classe comprend l’ensemble des
variables définies dans un bloc. C’est le cas de toute
variable définie à l’intérieur d’une fonction. L’espace
mémoire réservé pour ce type de variable est alloué dans
la pile d’exécution. C’est pourquoi elles sont appelées
aussi auto c.a.d automatique car l’espace mémoire
associé est créé lors de l’entrée dans la fonction et il
est détruit lors de la sortie de la fonction. La durée
de vie des variables de type local est celle de la
fonction dans laquelle elles sont définies.

16
Les classes d’allocation
mémoire

static ce qualificatif modifie la visibilité de la


variable, ou son implantation :
– dans le cas d’une variable locale, il modifie
son implantation en lui attribuant une partie de
l’espace de mémoire globale. Une variable
locale de classe statique a un nom local
mais a une durée de vie égale à celle du
programme en cours d’exécution.
– dans le cas d’une variable globale, ce
prédicat restreint la visibilité du nom de
la variable à l’unité de compilation. Une
variable globale de type statique ne peut pas
être utilisée par un autre fichier source
participant au même programme par une
référence avec le mot réservé extern (voir
point suivant).

17
Exemple sur l’utilisation de
static

int a; /*variable globale*/


void f1(void){
static long a = 1;/*variable statique locale à
f1*/
a + = 10; /*modification locale à f1*/
}
void f2(void){
for(a = 1;a<10;a++)/*modification globale*/
f1();
}
main()
{
f2();
}

18
Exemple sur l’utilisation de
static : commentaire

La durée de vie d’une variable locale statique est la même


que celle des variables globales. A chaque appel, la
fonction retrouve la valeur de la variable locale statique
qu’elle a modifiée lors des appels précédents.
L’initialisation d’une variable statique interne à une
fonction est faite à la compilation, et non à l’entrée
dans la fonction.
Lorsque le programme s’exécute, la variable entière a
global prend successivement les valeurs : 1 2 3 4 5 6 7 8
9
la variable a locale à f1 prend successivement les valeurs
: 1 11 21 31 41 51 61 71 81 91.
Nous pouvons donc avoir une variable interne à une
fonction qui compte le nombre d’appels à cette fonction.

19
Les classes extern et registre
extern ce qualificatif permet de spécifier que la ligne
correspondante n’est pas une tentative de définition mais
une déclaration . Il précise les variables globales
(noms et types) ou fonctions qui sont définies dans
un autre fichier source et qui sont utilisées dans ce
fichier source.

register ce qualificatif permet d’informer le compilateur


que les variables locales définies dans le reste de la ligne
sont utilisées souvent. Le prédicat demande de les
mettre si possible dans des registres disponibles du
processeur de manière à optimiser le temps
d’exécution. Le nombre de registres disponibles pour de
telles demandes est variable selon les machines. Il est de
toute façon limité (4 pour les données, 4 pour les pointeurs
sur un 680X0). Seules les variables locales peuvent être
qualifiées register.

20
Qualificatifs d’aide au
compilateur
Une définition de variable qualifiée du mot const informe le compilateur
que cette variable est considérée comme constante et ne doit pas être
utilisée dans la partie gauche d’une affectation. Ce type de définition
autorise le compilateur à placer la variable dans une zone
mémoire accessible en lecture seulement à l’exécution.
Le qualificatif volatile informe le compilateur que la variable
correspondante est placée dans une zone de mémoire qui peut
être modifiée par d’autres parties du système que le programme
lui-même. Ceci supprime les optimisations faites par le compilateur lors
de l’accès en lecture de la variable. Ce type de variable sert à décrire des
zones de mémoire partagées entre plusieurs programmes ou encore des
espaces mémoires correspondant à des zones d’entrées-sorties de la
machine.
Les deux qualificatifs peuvent être utilisés sur la même variable, spécifiant
que la variable n’est pas modifiée par la partie correspondante du
programme mais par l’extérieur.

21
Les pointeurs

22
Introduction

Une variable est destinée à contenir une valeur


du type avec lequel elle est déclarée.
Physiquement cette valeur se situe en
mémoire.

Prenons comme exemple un entier nommé x :


int x;
/*Réserve un emplacement en mémoire pour un
entier.*/
x = 10;
/*Ecrit la valeur 10 dans l'emplacement
réservé. */

23
Illustration

Pour obtenir l'adresse d'une variable on


fait précéder son nom par l'opérateur '&'
(adresse de) :
Pour afficher l’adresse de x, on exécute
l’instruction:
printf("%p",&x); Ce qui dans le cas du
schéma ci-dessus, afficherait 62.

24
Pointeur - définition

Variable spéciale destinée à contenir


une adresse mémoire d’un objet. c'est
à dire une valeur identifiant un
emplacement en mémoire. On la déclare
ainsi :

type *identifiant;
définit identifiant comme variable de
type pointeur vers type

type est le type de l’objet pointé

25
Exemple

Considérons cette déclaration : int *px;

Elle précise que px est une variable de type


pointeur sur des objets de type int
Elle réserve un emplacement mémoire pour px
où stocker une adresse mémoire.

La valeur de px est pour l’instant indéfinie

26
Attribuer une valeur à une
variable de type pointeur

Il est conseillé d’initialiser un pointeur avec


la valeur NULL avant son utilisation effective
(constante prédéfinie qui vaut 0) ce qui, par
convention, indique que le pointeur ne
pointe sur rien.
px = NULL;

Pour assigner une valeur à px et, donc, la


faire pointer sur un entier précis, il existe
deux démarches de nature très différente.

27
Première démarche

Affecter à px l’adresse d’un objet


px = &x; // Ecrit l'adresse de x dans cet
emplacement.
printf("%d",*px); //Affiche la valeur de x par pointeur
déréférencé (10 dans le cas du schéma).
Remarque
Lors de la déclaration d'un pointeur en C, ce pointeur est lié
explicitement à un type de données. Ainsi, la variable px
déclarée comme pointeur sur int ne peut pas recevoir
l'adresse d'une variable d'un autre type que int.

28
Seconde démarche

Utiliser
des fonctions d’allocation dynamique. Par exemple :

px =
malloc(sizeof(int));
permet
d’allouer un espace mémoire de la taille d’un entier et
affecte son adresse à px.

Autre
façon, si on déclare deux pointeurs :
int *ad1,
*ad2;
ad1 =
ad2;
ad2
pointe alors sur le même objet que ad1

29
Illustration
200

Soit : int *p; En mémoire ???

L’instruction suivante permet de


réserver la mémoire pour l’objet qui sera
pointé par p:
p = (int*) malloc(sizeof(int))
200 55
En mémoire
55

Schématiquement:
p

30
L’opérateur * pour manipuler
un objet pointé

Si ad est un pointeur, *ad désigne l’objet


pointé par ad.
int *ad, *p, n=20;
…….
ad = &n;
printf(" %d" , *ad); /*affiche la valeur 20*/

p = (int*)malloc(sizeof(int));
*p = 30; /*place la valeur 30 dans
l’emplacement préalablement alloué*/

31
Illustration
*p
Valeur de ad
Adresses mémoire

Valeur de p

33 55

33 55 20 30

ad p x

*ad valeur de x

Schématiquement:
Objet pointé par ad

p ad 20
30
x
Objet pointé par p
32
Exemple

Si un pointeur P
pointe sur une variable X, alors *P peut être utilisé partout où on peut
écrire X.
Les expressions
suivantes, sont équivalentes:
Y = *P+1 Y =
X+1
*P = *P+10 X =
X+10
*P += 2 X +=
2
++*P ++X
(*P)++ X++
Dans le dernier cas, les
parenthèses sont nécessaires:
Comme les opérateurs
unaires * et ++ sont évalués de droite à gauche, sans les parenthèses le pointeur P
serait incrémenté, non pas l'objet sur lequel P pointe.

33
Priorité de * et &

es opérateurs * et & ont la même


priorité que les autres opérateurs
unaires (la négation !,
l'incrémentation ++, la
décrémentation --). Dans une même
expression, les opérateurs unaires *,
&, !, ++, -- sont évalués de droite à
gauche.

34
Déclarateur de forme pointeur

D’une manière générale, le type d’un


pointeur est défini par une déclaration
qui associe un déclarateur à un
spécificateur de type.

On parlera de déclarateur de forme


pointeur:

* déclarateur

35
Cas simples : pointeurs sur des objets
d’un type de base, structure ou défini
par typedef :

Le déclarateur correspond à un simple identificateur

unsigned int n, ad et ptr sont des


*ptr, q, *ad; pointeurs sur des
éléments de type
unsigned int
struct point{char ads est un pointeur sur
nom; des structures de type
int x; struct point
int y; s est de type struct point
};
struct point *ads, s;
typedef struct point POINT est synonyme de
POINT; struct point
POINT * adp; adp est un pointeur sur
des structures de type
struct point 36
Pointeur de pointeur
On peur composer deux fois de suite le
déclarateur de pointeur

float x, *adf, **adadf; **adadf est un float


*adadf est un
pointeur sur un float
adadf est un pointeur
sur un pointeur sur un
float
int (**p)[5]; (**p)[5] est un int
(**p)est un tableau de 5
(facultatif) int
**p est un tableau de 5
int
*p est un pointeur sur un
tableau de 5 int
p est un pointeur sur un
pointeur sur un tableau
37
de 5 int
Pointeurs sur des tableaux ( facultatif )
Les déclarateurs peuvent se composer à
volonté

int n, *ad, (*chose) (*chose)[10]est un int


[10]; (*chose) est un tableau
de 10 int
*chose est un tableau
de 10 int
Chose est un pointeur
sur un tableau de 10
int
typedef int vecteur est synonyme de
vecteur[3]; int[3]
vecteur * adv; adv est un pointeur sur
des tableaux de 3 int

38
Déclarateur de forme pointeur ne
correspondant pas à un pointeur
int *chose[10]est un int
*chose[10]; chose[10]est un pointeur sur
un int
chose est un tableau de 10
pointeurs sur un int

int **chose[10]est un int


**chose[10]; *chose[10]est un pointeur sur un
int
( facultatif ) chose[10]est un pointeur sur un
pointeur sur un int
chose est un tableau de 10
pointeurs sur un pointeur sur un
int
int * f(float); *f(float) est un int
f(float) est un pointeur sur un
int(on doit interprêter le
déclarateur pointeur en premier)
39
f est une fonction recevant un
Les propriétés arithmétiques
des pointeurs
Soit la déclaration suivante : int *ad;

L’expression ad + 1 est du même type que ad


et sa valeur est celle de l’entier suivant
l’entier pointé actuellement par ad.
La différence entre les deux adresses ad et
ad+1 est de sizeof(int) octets.

De même, l’expression ad+3 pointerait sur un


emplacement situé 3 entiers plus loin que
celui pointé par ad.

ad++ est équivalente à ad = ad+1;

40
Soustraction de deux pointeurs
Il est possible de soustraire deux pointeurs de même type.

Soit la déclaration suivante :


int t[10];
int *ad1 = &t[0], *ad2 = &t[5];

L’expression ad2 – ad1 a pour valeur 5


Plus généralement, la soustraction de deux pointeurs qui
pointent dans le même tableau est équivalente à la
soustraction des indices correspondants.
NB.Le résultat de la soustraction p1-p2 est :
négatif, si p1 précède p2
zéro, si p1 = p2
positif, si p2 précède p1
indéfini, si p1 et p2 ne pointent pas dans le même tableau

41
Comparaison de deux pointeurs

 On peut comparer deux pointeurs par <, >, <=, >=, ==, !=.
 La comparaison de deux pointeurs qui pointent dans le même
tableau est équivalente à la comparaison des indices
correspondants. (Si les pointeurs ne pointent pas dans le même
tableau, alors le résultat est donné par leurs positions relatives
dans la mémoire).
NB.
 Comme les pointeurs jouent un rôle si important, le langage C
soutient une série d'opérations arithmétiques sur les pointeurs que
l'on ne rencontre en général que dans les langages machines. Le
confort de ces opérations en C est basé sur le principe suivant:
 Toutes les opérations avec les pointeurs tiennent compte
automatiquement du type et de la grandeur des objets
pointés.

42
Domaine des opérations
L'addition, la soustraction, l'incrémentation et la décrémentation sur les
pointeurs sont seulement définies à l'intérieur d'un tableau. Si l'adresse
formée par le pointeur et l'indice sort du domaine du tableau, alors le
résultat n'est pas défini.
Seule exception: Il est permis de 'pointer' sur le premier octet derrière un
tableau (à condition que cet octet se trouve dans le même segment de
mémoire que le tableau). Cette règle, introduite avec le standard ANSI-C,
légalise la définition de boucles qui incrémentent le pointeur avant
l'évaluation de la condition d'arrêt.
Exemples : int A[10]; int *P;
P = &A[9]; /* dernier élément -> légal */
P = &A[9]+1;/* dernier élément + 1 -> légal */
P = &A[9]+2; /* dernier élément + 2 -> illégal */
P = &A[0]-1; /* premier élément - 1 -> illégal */

43
Pointeurs et structures

Les objets de type structure en C sont des


Lvalues. Ils possèdent une adresse, c’est
l'adresse du premier élément du premier
champ de la structure.

L’allocation dynamique de la mémoire pour


un objet de type struct nom {. . .}; se fait
de la même manière que pour un pointeur
quelconque:

struct nom *p;

p = (struct nom *)malloc(sizeof(struct


nom));

44
Pointeurs et structures

Si p est un pointeur sur une structure, on peut


accéder à un champ de la structure pointé par
l'expression : (*p).champ
Exemple :
article *p;
Pour accéder à prix : (*p).prix
L'usage de parenthèses est ici indispensable car
l'opérateur d'indirection * a une priorité moins
élevée que l'opérateur de membre de structure.

La notation (*p).champ peut être simplifiée grâce à


l'opérateur pointeur de membre de structure, noté
->. Cette expression est équivalente à p->champ

Exemple :
(*p).prix p->prix

45
TABLEAUX ET POINTEURS

Le nom d'un tableau est considéré


comme un pointeur (constant) sur son
premier élément (Dans la réalité le nom
d'un tableau référence un emplacement
en mémoire).

46
Equivalence entre référence à
un tableau et pointeur

Régle générale
Lorsqu’un nom de tableau apparait dans un
programme, il est converti en un pointeur constant
sur son premier élément
Supposons ces déclarations:
int t[10], * adr;
Voici quelques expressions équivalentes :
scanf(" %d" , t); scanf(" %d" , &t[0]);
t+i &t[i]
*(t+i) t[i]
adr[i] *(adr+i).
adr = t+3; /*adr pointe sur le troisième élément
de t*/
*adr = 50; t[3] = 50

47
TABLEAUX ET POINTEURS

A 6
int A[10], *p; 8
4
p = A;
3
p
9
1
p prend l’adresse du premier élément
de t 5
NB. Les noms de tableaux étant des
constantes, il n’est pas possible de les 4
affecter
3
2

48
TABLEAUX ET POINTEURS

A
7
p[0]++; 8
p
4
*(p) ou p[0]désigne le contenu de
A[0] 3
*(p+1) désigne le contenu de A[1] 9
*(p+2) désigne le contenu de A[2] 1

5
*(p+i) désigne le contenu de A[i]
4
3
2

49
TABLEAUX ET POINTEURS

p = &A[4]; A
6
8
4
p
Si p pointe sur une composante 3
quelconque d'un tableau, alors p+1 pointe
sur la composante suivante.
9
Plus généralement, p+i pointe sur la ième 1
composante derrière p et p-i pointe sur la
ième composante devant p 5
4
3
2

50
Remarque

 Au premier coup d'oeil, il est bien surprenant que p+i n'adresse


pas le i-ième octet derrière p, mais la i-ième composante
derrière p ...
 Ceci s'explique par la stratégie de programmation 'défensive'
des créateurs du langage C:
 Si on travaille avec des pointeurs, les erreurs les plus perfides
sont causées par des pointeurs mal placés et des adresses mal
calculées. En C, le compilateur peut calculer automatiquement
l'adresse de l'élément p+i en ajoutant à p la grandeur d'une
composante multipliée par i. Ceci est possible, parce que:
 - chaque pointeur est limité à un seul type de données, et
 - le compilateur connaît le nombre d'octets des différents types.

51
TABLEAUX ET POINTEURS

p++; A 6
p 8
4
3
9
le pointeur contient
maintenant l'adresse du 1
deuxième élément du tableau 5
!on ne peut pas incrémenter le 4
pointeur A car c'est un
pointeur constant. 3
2

52
Remarques
 Il existe toujours une différence essentielle entre un pointeur et le nom
d'un tableau:
 - Un pointeur est une variable,
donc des opérations comme P = A ou P++ sont permises.
 - Le nom d'un tableau est une constante,
donc des opérations comme A = P ou A++ sont impossibles.
 Lors de la première phase de la compilation, toutes les expressions de la
forme A[i] sont traduites en *(A+i). En multipliant l'indice i par la
grandeur d'une composante, on obtient un indice en octets:
 <indice en octets> = <indice élément> * <grandeur élément>
 Cet indice est ajouté à l'adresse du premier élément du tableau pour
obtenir l'adresse de la composante i du tableau. Pour le calcul d'une
adresse donnée par une adresse plus un indice en octets, on utilise un
mode d'adressage spécial connu sous le nom 'adressage indexé':
 <adresse indexée> = <adresse> + <indice en octets>
 Presque tous les processeurs disposent de plusieurs registres spéciaux
(registres index) à l'aide desquels on peut effectuer l'adressage indexé
de façon très efficace.

53
Formalisme tableau -
formalisme pointeur

void main() void main()


{ {
int T[10] = {-3, 4, 0, int T[10] = {-3, 4, 0,
-7, 3, 8, 0, -1, 4, -9}; -7, 3, 8, 0, -1, 4, -9};
int POS[10]; int POS[10];
int I,J; /* indices int I,J;
courants dans T et for (J=0,I=0 ; I<10 ;
POS */ I++)
for (J=0,I=0 ; I<10 ; I+ if (*(T+I)>0) {
+) *(POS+J) =
if (T[I]>0) { *(T+I);
POS[J] = T[I]; J++;
J++; }
} }
}

La notation tableau[I] est systématiquement remplacée par *(tableau + I)


54
Exemple de manipulation

int t[10];
int i;
for (i = 0 ; i < 10 ; i++)
*(t+i) = 1;

int i; int *p;


for (p = t, i = 0 ; i < 10 ; i++, p++)
*p = 1;
int *p;
for (p = t; p <t+ 10 ; p++)
*p = 1;

55
Pointeurs et chaines de
caractères

En C, une chaîne de caractères est représentée par


une suite d’octets correspondant à chacun de ses
caractères, le tout étant terminé par un octet
supplémentaire de code nul.
De plus, une chaîne telle que « bonjour » sera
traduite par le compilateur en un pointeur sur la
zone mémoire représentant une telle chaîne.
char * adr ;
adr = " bonjour ";
La notation "bonjour " a comme valeur l’adresse de
la chaîne constante

NB.
char ch[20];
ch = " bonjour "; //incorrect
char ch[20] = " bonjour";//correct

56
Tableaux de pointeurs
Si nous avons besoin d’un ensemble de pointeurs, nous pouvons
les réunir dans un tableau de pointeurs.
Type *NomTableau[N]
déclare un tableau NomTableau de N pointeurs sur
des données du type Type.
Exemple
double *A[10];
déclare un tableau de 10 pointeurs sur des rationnels du
type double dont les adresses et les valeurs ne sont pas
encore définies.
Remarque
Le plus souvent, les tableaux de pointeurs sont utilisés
pour mémoriser de façon économique des chaînes de
caractères de différentes longueurs.

57
Tableaux de pointeurs

 Les tableaux de pointeurs double *A[];


déclare un tableau de pointeurs sur des éléments du
type double
 A[i] peut pointer sur des variables simples ou sur les
composantes d'un tableau.
 A[i] désigne l'adresse contenue dans l'élément i de A (Les
adresses dans A[i] sont variables)
 *A[i] désigne le contenu de l’objet d’adresse A[i]
 Si A[i] pointe dans un tableau,
 A[i] désigne l'adresse de la première composante
 A[i]+j désigne l'adresse de la j-ième composante
 *(A[i]+j) désigne le contenu de la j-ième composante

58
Tableaux de pointeurs sur char

Nous pouvons initialiser les pointeurs d'un


tableau sur char par les adresses de chaînes
de caractères constantes.
Exemple
char *JOUR[] = {"dimanche", "lundi",
"mardi", "mercredi", "jeudi",
"vendredi", "samedi"};
déclare un tableau JOUR[] de 7 pointeurs sur
char. Chacun des pointeurs est initialisé avec
l'adresse de l'une des 7 chaînes de
caractères.

59
Illustration

60
Exemple de manipulation

On
peut afficher les 7 chaînes de caractères en fournissant les adresses
contenues dans le tableau JOUR à printf (ou puts) :
for
(int I=0; I<7; I++) printf("%s\n", JOUR[I]);

Comm
e JOUR[I] est un pointeur sur char, on peut afficher les premières
lettres des jours de la semaine en utilisant l'opérateur 'contenu de' :
for
(I=0; I<7; I++) printf("%c\n", *JOUR[I]);

L'expre
ssion JOUR[I]+J désigne la J-ième lettre de la I-ième chaîne. On peut
afficher la troisième lettre de chaque jour de la semaine par:
for
(int I=0; i<7; I++) printf("%c\n",*(JOUR[I]+2));

61
Exemple
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main()
{ int i; char *chaine1, *chaine2, *res, *p;
chaine1 = "chaine ";
chaine2 = "de caracteres";
res = (char*)malloc((strlen(chaine1) + strlen(chaine2))
* sizeof(char));
p = res;
for (i = 0; i < strlen(chaine1); i++)
*p++ = chaine1[i];
for (i = 0; i < strlen(chaine2); i++)
*p++ = chaine2[i];
*p = '\0';
printf("%s\n",res);
}

62
La gestion dynamique de la
mémoire

Trois types de données :


Statiques
Automatiques
Dynamiques

63
déclaration statique de
données

La
mémoire réservée pour les données déclarées d’une
manière statique est connue à la compilation.
Exe
mple :
float
A, B, C; /* réservation de 12 octets */
shor
t D[10][20]; /* réservation de 400 octets */
char
E[] = {"Bonjour !"};
/*
réservation de 10 octets */
char
F[][10] = {"un", "deux", "trois", "quatre"}; /*
réservation de 40 octets */

64
Déclaration d’un pointeur

Le
nombre d’octets à réserver pour un pointeur dépend de la
machine et du modèle de mémoire choisi, mais il est
connu à la compilation. Un pointeur est aussi déclaré
statiquement. Supposons qu’un pointeur ait besoin de p
octets en mémoire ( en général p = 4 )

Exempl
es :

double
*G; /* réservation de p octets */
char
*H; /* réservation de p octets */
float
*I[10]; /* réservation de 10*p octets */

65
Chaines de caractères
constantes

L
’espace pour les chaines de caractères constantes
qui sont affectées à des pointeurs ou utilisées
pour initialiser des pointeurs sur char est aussi
réservé automatiquement c. à. d à la compilation.

E
xemples :

c
har *J = "Bonjour ! "; /* réservation de p+10
octets */
c
har *K[] = {"un", "deux", "trois", "quatre"}; /*
réservation de 4*p+3+5+6+7 octets */

66
Position du problème
Souvent nous devons travailler avec des données dont nous ne
pouvons prévoir le nombre et la grandeur lors de la programmation.
Exemple
Nous voulons lire 10 phrases : le nombre de caractères par phrase
est imprévisible. Nous pouvons mémoriser les phrases en utilisant
un tableau de pointeurs sur char. Nous déclarons ce tableau de
pointeurs par : char *texte[10];
Pour les 10 pointeurs, nous avons besoin de 10*p octets. Ce nombre
est connu dès le départ et les octets sont réservés
automatiquement. Il nous est cependant impossible de prévoir à
l’avance le nombre d’octets à réserver pour les phrases elles même
qui seront introduites lors de l’exécution du programme.

67
Données dynamiques

La gestion des données dynamiques se fait


généralement dans ce que l’on nomme un tas
(heap) dans lequel on cherche à allouer ou à libérer
de l’espace en fonction des besoins.

Avantages des données dynamiques:

Possibilité de définir des tableaux de dimension


variable c. à. d. non connues à la compilation.
Mise en œuvre de listes chainées, d’arbres
binaires . . .

Le tas est l'espace en mémoire laissé libre une fois mis en place le
DOS, les gestionnaires, les programmes résidents, le programme lui-
même et la pile (stack).

68
Allocation dynamique de la
mémoire

La bibliothèque stdlib fournit des fonctions qui


permettent de réserver et de libérer de manière
dynamique (à l’exécution) la mémoire.

la fonction malloc

Syntaxe : void * malloc(unsigned int taille)


malloc réserve une zone de taille octets en
mémoire et renvoie l’adresse du début de la
zone sous forme d’un pointeur non typé (ou
NULL si l’opération n’est pas possible)
le paramètre taille est du type unsigned int.
A l'aide de malloc, nous ne pouvons donc pas
réserver plus de 65535 octets à la fois!

69
Allocation dynamique de la
mémoire

En pratique, on a besoin du type d’un pointeur


pour pouvoir l’utiliser. On souhaite d’autre part ne
pas avoir à préciser la taille de la mémoire en
octets surtout s’il s’agit de structures. L’usage
consiste donc, pour réserver de la mémoire pour
une variable d’un type donné à:
Déclarer un pointeur du type voulu

Utiliser la fonction sizeof ()qui renvoie la taille en


octets du type passé en paramètre
Forcer malloc à renvoyer un pointeur du type
désiré
Sur un IBM-PC (ou compatible )
Sizeof(4.25) s’évalue à 8

Sizeof(« bonjour ») s’évalue à 10

Sizeof(float) s’évalue à 4

Sizeof(double) s’évalue à 8

70
La fonction free

int *p;
p =(int *) malloc(10*sizeof(int));
if(p) {
for ( i=0 ; i < 10 ; i++){
p[i] = i; printf("%d",p[i]);
}
free(p);
}
L'instruction malloc retourne un pointeur sur une
zone mémoire de la taille de 10 entiers. Ce qui
correspond à un tableau de 10 entiers.
Le rôle de la fonction free() est de de libérer un
emplacement préalablement alloué. (n’a pas d’effet
si le pointeur a la valeur zéro)
Syntaxe : void free(void* p)

71
La fonction free

La fonction free peut aboutir à un


désastre si on essaie de libérer de la
mémoire qui n’a pas été allouée par
malloc.
La fonction free ne change pas le contenu
du pointeur; il est conseillé d’affecter la
valeur zéro au pointeur immédiatement
après avoir libérée le bloc de mémoire qui
y était attaché.
Si nous ne libérons pas explicitement la
mémoire à l’aide de la fonction free, alors
elle est libérée automatiquement à la fin
du programme.

72
La fonction exit

S’il n’y a pas assez de mémoire pour effectuer


une action avec succés, il est conseillé
d’interrompre l’exécution du programme à l’aide
de la fonction exit de (stdlib.h) et de renvoyer
une valeur différente de zéro comme code
d’erreur du programme.
Exemple
Le programme suivant lit 10 phrases au clavier,
recherche des blocs de mémoire libres assez
grands pour la mémorisation et passe les
adresses aux composantes du tableau TEXTE[].
S’il n’y a pas assez de mémoire pour une chaine,
le programme affiche un message d’erreur et
interrompt le programme avec le code d’erreur
-1.

73
La fonction exit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{char INTRO[500]; char *TEXTE[10]; int I;
for (I=0; I<10; I++) {
gets(INTRO);
TEXTE[I] = malloc(strlen(INTRO)+1); /* Réservation de la
mémoire */
if (TEXTE[I]) /* S'il y a assez de mémoire, ... */
strcpy(TEXTE[I], INTRO); /* copier la phrase à l'adresse
fournie par malloc, ...*/
else{/* sinon quitter le programme après un message d'erreur.
*/ printf("ERREUR: Pas assez de mémoire \n");
exit(-1);
}
}
}

74
Fonction calloc

La fonction calloc de la librairie stdlib.h a le


même rôle que malloc mais en plus elle
initialise l’objet pointé *p à zéro. L'emploi
de calloc est simplement plus rapide
Syntaxe :
calloc(unsigned int nb,unsigned int taille)

p = (int*)calloc(N,sizeof(int));

p = (int*)malloc(N * sizeof(int));
for (i = 0; i < N; i++)
*(p + i) = 0;

75
La fonction realloc

La fonction realloc permet de


modifier la taille d’une zone
préalablement allouée
Syntaxe :
void *realloc(void *pointeur,
unsigned int taille)

p = (int*)malloc(5*sizeof(int));

p = realloc(p, sizeof(int));

76
Tableaux multidimensionnels

Un tableau à deux dimensions est, par définition,


un tableau de tableaux.
Il s'agit donc en fait d'un pointeur vers un
pointeur.
Dans la déclaration double mat[NL][NC];
Le compilateur considère que mat désigne un
tableau de NL éléments, chacun étant un tableau
de NC doubles
mat pointe vers un objet lui-même de type
pointeur de doubles.
mat est l'adresse du premier élément du tableau
= &mat[0][0].
mat[i] est l’adresse du premier élément de la
ligne d'indice i. mat[i] = &mat[i][0] = mat +i .
mat+1 correspond donc à l’adresse mat
augmentée de NC entiers ≠ &mat[0][0] + 1

77
Adresses des sous-tableaux et Noms des
différents éléments du tableau mat

mat
mat[0][0]mat[0][1]mat[0][2]mat[0][3]mat[0][4]
mat[0]
mat[1][0]mat[1][1]mat[1][2]mat[1][3]mat[1][4]
mat+1

mat+2

mat+3

78
Pointeur vers pointeur

 On déclare un pointeur qui


pointe sur un objet de type type
* de la même manière qu'un
pointeur, c'est-à-dire type
**nom_du_pointeur;
 Exactement comme pour les
tableaux à une dimension, les
pointeurs de pointeurs ont de
nombreux avantages sur les
tableaux multidimensionnels (la
première c’est qu’ils sont
variables).

79
Pointeurs et tableaux
multidimensionnels

void pointeurmatrice(int nl,int nc) {


double **pt;
int i,j;
/*initialisation des pointeurs*/
pt = (double **)malloc(nl * sizeof(double *));
for (i=0;i<nl;i++)
pt[i] = (double *)malloc(nc *
sizeof(double));
/*utilisation de la matrice pt. Par exemple*/
for(i = 0; i < nl; i++)
for(j = 0; j < nc; j++)
pt[i][j] = 0;
/* libération (Indispensable dans le cas d’une
variable locale)*/
for (i = 0; i < nl; i++)
free(pt[i]);
free(pt);
}

80
Schéma illustratif de la
mémoire

pt

*pt *(pt+1) *(pt+2)

Pt[0]
=*pt Pt[1] =*(pt+1)
Pt[0][0]

Pt[1][0]
Pt[0][1]

Pt[1][1]

Pt[1][2]

81
Commentaire
 La première allocation dynamique réserve
pour l'objet pointé par pt l'espace mémoire pour
nl pointeurs vers des réels.
 Ces nl pointeurs correspondent aux lignes .
 Les allocations dynamiques suivantes
réservent pour chaque pointeur pt[i] l'espace
mémoire nécessaire pour stocker nc réels.
 On peut choisir des tailles différentes pour
chacune des lignes pt[i].
for (i = 0; i < nl; i++) pt[i] = (int*)malloc((i
+ 1) * sizeof(double));
 pour initialiser tous les éléments du tableau à
zéro, on met:
pt[i] = (double*)calloc(nc, sizeof(double));

82
Schéma illustratif : nl = 6 nc =
5

83
Pointeurs et tableaux
multidimensionnels

#include < stdio.h>


#include < stdlib.h>
main()
{ int nl, nc; double **pt;
pt = (double**)malloc(nl * sizeof(double*));
for (i = 0; i < nl; i++)
pt[i]= (double*)calloc(nc ,
sizeof(double));
for (i = 0; i < nl; i++)
free(pt[i]);
free(pt);
}

84
Pointeurs et fonctions

85
Simuler une transmission par
adresse avec un pointeur

Une autre utilité des pointeurs est de permettre à


des fonctions d'accéder aux données elles même
et non à des copies.
void echange(int *ad1, int *ad2)
{ int t;
t = *ad1; *ad1 = *ad2; *ad2 = t;
}
int x = 10, y = 11;
echange(&x, &y);
printf("x = %d y = %d\n",x,y); //11 10
La transmission se fait toujours par valeur, il
s’agit des valeurs des expressions &x et &y

86
Illustration

À l’appel
x 10 y 11

ad1 ad2

Début d’exécution

x 10 y 11

ad1 ad2

t = *ad1; 10 t

87
Illustration

x 11 y 11

*ad1 = *ad2;
ad1 ad2

t 10

x 11 y 10

ad1 ad2

*ad2 = t;
10 t

88
Tableaux transmis en
argument

Lorsqu’un tableau à un seul indice apparaît en


argument d’une fonction, le compilateur n’a pas
besoin de connaître la taille exacte.
Ainsi, on peut écrire indifféremment :
void fct( int t[10] );void fct( int *t );void fct( int t[ ] );
Fonction travaillant sur un tableau de taille variable
int som (int t[ ], int nb) {
int s = 0, i;
for(i=0 ; i < nb ; i++)
s += t[i];
return s;
}

89
Tableaux transmis en
argument

Lorsqu’un tableau à plusieurs indices


apparaît en argument d’une fonction, le
compilateur n’a pas besoin de connaître
la première dimension.
Ainsi, on peut écrire indifféremment :

void ft(int t[10][15]);


Ou
void ft(int t[ ][15]);

90
TRANSMISSION DE LA VALEUR
D’UNE STRUCTURE

#include <stdio.h>
Struct enreg { int a;double b;}
int main (void)
{ struct enreg x;
void fct (struct enreg y);
x.a = 1; x.b = 12.5;
printf(" \navant appel fct : %d %e",x.a,x.b);
fct(x);
printf(" \nretour à main: %d %e: "x.a,x.b);
Return 0;
}
void fct(struct enreg s){
s.a = 0; s.b = 1;
Printf(" \ndans fct %d %e ", s.a, s.b);
}

91
TRANSMISSION DE L’ADRESSE
D’UNE STRUCTURE

#include <stdio.h>
Struct enreg { int a;double b;}
int main (void)
{ struct enreg x;
void fct (struct enreg *);
x.a = 1; x.b = 12.5;
printf(" \navant appel fct : %d %e",x.a,x.b);
fct(&x);
printf(" \nau retour à main: %d %e: "x.a,x.b);
return 0;
}
void fct(struct enreg *ps){
ps->a = 0; ps->b = 1;
Printf(" \ndans fct %d %e ", ps->a, ps->b);
}

92
Exécution

Transmission par valeur


Avant appel fct 1 1.25000e+01
Dans fct 0 1.00000e+00
Au retour dans main 1 1.2500e+01

Transmission par adresse


Avant appel fct 1 1.25000e+01
Dans fct 0 1.00000e+00
Au retour dans main 0 1.0000e+00

93
Fonction qui renvoie un objet
de type structure.

Soit : struct complexe


{double reel,image;}
.
Struct complexe conjugue(struct
complexe x};
{ struct complexe y;
y.reel = x.reel;
y.image = -x.image;
return y;
}

Struct complexe x,z;


z = complexe(x);

94
Exemple: tableau et structures

#include <stdio.h>
#include <stdlib.h>
Typedef struct
{ int numero, qte;
double prix;
}article;
main()
{ int n, i; article* tab;
printf("nombre d‘articles = ");
scanf("%d",&n);
tab = (article *)malloc(n * sizeof(article));
/* saisie des articles dans un tableau*/
for (i =0 ; i < n; i++) {

95
Exemple: tableau et structures

printf("\n saisie de l’article %d\n",i);


tab[i].numero = i;
Printf("quantité = "); scanf("%d",&tab[i].qte);
printf("\n prix = ");
scanf("%lf",&tab[i].prix);
}
/*affichage d’un article de numéro donné*/
printf("\n Entrez un numero ");
scanf("%d",&i); printf("\n article numéro %d:",i);
printf("\n prix unitaire = %.2f",tab[i].prix);
printf("\n quantité = %d\n",tab[i].qte);
free(tab);
}

96
Remarques

Même si dans un système donné un


pointeur a toujours la même taille
(4 octets pour un système à
adressage sur 32 bits), le langage
impose de leur donner un type.

Si on ne sait pas à l'avance sur quel


type de données le pointeur va
pointer, on peut lui donner le type
void. (diapo. pointeur générique)

97
Pointeurs génériques

98
Pointeurs génériques

 C ne permet les affectations entre


pointeurs que si ceux-ci sont de même
type. Pour écrire des fonctions
indépendantes d’un type particulier (par
exemple une fonction d’échange) le
mécanisme de typage peut être contourné
utilisant des pointeurs génériques.

 Pour créer un pointeur générique, il faut le


déclarer de type void*

99
Pointeurs génériques

 Une variable de type void * ne peut pas intervenir


dans des opérations arithmétiques.
void raz(void *adr, int n)
for (int i=0; i<n ; i++,adr++) *adr = 0;
}//cette fonction est illégale
On doit plutôt utiliser une variable de type char * pour
décrire la zone à traiter :
void raz(void *adr, int n)
char *ad = adr;
for (int i=0; i<n ;i++,ad++) *ad = 0;
}
Exemple d’utilisation
void raz(void *, int); int t[10]; double z;
raz(t, 10*sizeof(int));
raz(&z, sizeof(z));

100
La fonction memcpy

 Il n’est pas possible d’appliquer l’opérateur


de déréférencement * à une variable de type
void * et donc d’accéder au contenu de la
variable pointée. Ceci pose un problème si
l’on veut copier des données d’une variable
désignée par un pointeur générique vers une
autre.

 La librairie string.h fournit une fonction qui


permet de résoudre ce problème : memcpy():
 Syntaxe: void *memcpy(void *pa, void *pb,
unsigned int n)
 memcpy copie n octets de l’adresse pb vers
l’adresse pa et retourne pa.

101
Applications

 Prenons l’exemple de la fonction


echange. Si l’on n’utilise pas de
pointeurs génériques, il faut écrire
autant de versions de cette fonction
que de types de variables à
permuter.
 Pour pouvoir utiliser la fonction
quelque soit le type des données à
manipuler il est nécessaire de
passer en paramètre la taille des
données à échanger qui dépend de
leur types.

102
Fonction echange version
générique

void echange (void* pa, void* pb, int


taille)
{
void* pc;
pc = malloc(taille);
memcpy(pc,pa,taille);
memcpy(pa,pb,taille);
memcpy(pb,pc,taille);
free(pc);
}

103
Utilisation

Ainsi en déclarant :
int i =2, j=3; float x=3.4, y=6.5;
Struct complexe cx={1,2},cy={3,4};
L’appel peut être effectué pour
différents types de données
echange(&i,&j,sizeof(i));
echange(&x,&y,sizeof(x));
echange(&cx,&cy,sizeof(cx);

! Sizeof() renvoie la taille du type de


la variable passée en paramètre.

104
Les pointeurs de fonction

105
Les pointeurs de fonction

 La valeur renvoyée par le nom (seul)


d'une fonction est l'adresse de son code
en mémoire. les pointeurs de fonction
sont des pointeurs qui, au lieu de pointer
vers des données pointent vers du code
exécutable.

 La déclaration d’un pointeur de fonction


ressemble à celle d’une fonction sauf
que l’identificateur de la fonction est
remplacé par l’identificateur du pointeur
précédé d’un astérisque (*) le tout mis
entre parenthèses.

106
Pointeurs de fonctions

 int (* p_fonction) (int x, int y); déclare un


pointeur vers une fonction de type entier
nécessitant deux paramètres de type
entier.

 Au pointeur ainsi déclaré doit ensuite être


affectée l'adresse d'une fonction ayant la
même signature c'est-à-dire une fonction
recevant en paramètre deux entiers et
retournant un entier.

 Le pointeur s'utilise alors avec la même


syntaxe que la fonction.

107
Exemples d’utilisation

int(*pmax)(int*, int);
pmax = max;
pmax prend comme valeur l’adresse du
code exécutable de la fonction max

pmax(Tab,10) max(tab,10)

L’intérêt des pointeurs de fonction


est, surtout, de faire passer en
paramètre d’une fonction une autre
fonction.
( diapo qui suit )

108
Recherche du minimum d’une
fonction y =f(x) entre deux
bornes- sans pointeur de fonction

#include <stdio.h>
float carre(float x)
{ return x*x; }
float parabole(float x)
{ return x∗x - 4 ∗ x + 2; }
float min_carre(float a, float b)
{ int i; float pas; float vmin; float valeur;
pas =(b-a)/100;
vmin = carre(a);
for (i =1 ; i < 101 ; i++){
valeur = carre(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}

109
Recherche du minimum d’une
fonction y =f(x) entre deux
bornes- sans pointeur de fonction
(suite)

float min_parabole(float a, float b)


{ int i; float pas; float vmin; float valeur;
pas =(b-a)/100;
vmin = parabole(a);
for (i =1; i<101; i++){
valeur=parabole(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}
int main( )
{ printf("%f\n",min_carre(-3.0, 3.0);
printf("%f\n",min_parabole(-3.0, 3.0);
return 0;
}

110
Exemple : Recherche du minimum
d’une fonction y =f(x) entre deux
bornes

#include <stdio.h>
float carre(float x)
{ return x*x; }
float parabole(float x)
{ return x∗x - 4 ∗ x + 2; }
float min_fct(float a, float b, float
(* pF) ( float x))
{ int i; float pas; float vmin; float
valeur;
pas =(b-a)/100;
vmin = pF(a);

111
Recherche du minimum d’une
fonction y =f(x) entre deux bornes
(suite)

for (i =1; i<101; i++)


{ valeur=pF(a+i*pas);
if (vmin > valeur)
vmin = valeur;
}
return vmin;
}
int main( )
{ printf("%f\n",min_fct(-3.0, 3.0, carre);
printf("%f\n",min_fct(-3.0, 3.0,
parabole);
return 0;
}

112
Les fichiers

113
Les fichiers

Un fichier désigne un ensemble


d’informations situé sur une mémoire de
masse telle que disque et disquette.
En C, un fichier n’est pas structuré à
l’avance pour ce qui est de son contenu.
Ses données sont simplement rangés sous
la forme d’une suite continue de
caractères (octets).
Les informations stockées dans un fichier
peuvent être de différents types.

114
Accès séquentiel - Accès direct

On distingue généralement deux


techniques de gestion de fichiers :
L’accès séquentiel : cette technique
consiste à traiter les informations dans
l’ordre où elles sont stockées dans le
fichier.
L’accès direct suppose la possibilité
de se placer directement sur
l’information souhaitée sans avoir à
parcourir toutes celles qui la précèdent.
En C, on peut utiliser les deux modes
d’accès sur un même fichier.

115
Fichiers binaires - Fichiers texte

Il existe deux façons de coder les


informations stockés dans un fichier:
En binaire : les informations sont codés
sous forme brut, sans aucune
transformation. Les fichiers concernés
sont dits binaires.
En ASCII : Les fonctions de lecture et
d’écriture accompagnent le transfert
d’information d’opérations de
formatage analogues à celles que
réalisent printf ou scanf. Les fichiers
concernés par ces opérations sont
appelés des fichiers texte.
Dans les fichiers texte, chaque octet
représente un caractère.

116
Déclaration
Lorsqu’un programme doit lire ou écrire des données dans
un fichier, ces données sont acheminées à leur destination
en passant par un tampon (buffer). Ce tampon est une
zone de mémoire RAM dans laquelle on range une quantité
(assez importante ) de données.
L’emplacement mémoire du tampon d’E/S d’un fichier est
donné par une variable structurée de type FILE.
Le type FILE est défini dans le header stdio.h. Ses champs
contiennent l’adresse du tampon ainsi que d’autres
informations sur le fichier concerné.
La déclaration d’un fichier se fait par l’instruction suivante:
FILE *f;
f est défini en tant que pointeur sur un objet de type FILE.
Cette déclaration réserve un emplacement pour le pointeur
f. f est une variable logique qui sera associée dans le
programme au fichier physique en mémoire.

117
Instruction d’ouverture d’un
fichier

La fonction suivante crée la structure de type


FILE ( le fichier )et en fournit l’adresse en
résultat. Si le fichier existe déjà, la fonction
permet l’ouverture du fichier.
FILE *fopen(char *nom, char *mode);
L’argument nom contiendra le nom du fichier
physique concerné qui peut comporter le
chemin du fichier.
L’argument mode indique la nature des
opérations que le programme devra exécuter
après ouverture du fichier.

Exemple :
FILE *f;
f = fopen(" exemple ", " wb ");

118
Le valeurs possibles de mode pour
les fichiers textes :

r : (read) lecture seule. Le fichier doit exister. Le pointeur


de position est positionné en lecture au début du fichier. Le
contenu du fichier n’est pas détruit.
w : (write) écriture seule(le fichier peut exister ou non. S’il
existe, son contenu est détruit. Le pointeur est positionné
en écriture au début du fichier )
w+ : lecture /écriture (destruction ancienne version )
r+ : lecture/écriture d’un fichier existant
a : (append) écriture seule (sans destruction du contenu du
fichier; le pointeur est positionné en fin de fichier)
a+ : lecture/écriture d’un fichier existant, le pointeur est
positionné à la fin du fichier.

119
Mode pour les fichiers binaires:

rb : lecture seule (read binaire)


wb : écriture seule (destruction
ancienne version ) (write binaire)
wb+ : lecture /écriture (destruction
ancienne version )
rb+ : lecture/écriture d’un fichier
existant
ab : écriture seule (sans destruction du
contenu du fichier; le pointeur est
positionné en fin de fichier)
ab+ : lecture/écriture d’un fichier
existant, le pointeur est positionné à la
fin du fichier.

120
Opérations de lecture et
d’écriture

Pour lire ou écrire des données dans un


fichier, on dispose de fonctions analogues aux
opérations d’entrée sorties habituelles. Outre
la lecture et écriture par caractère et avec
formatage, on dispose de fonctions de
transfert de blocs quelconques de données.
La position à laquelle on lit ou on écrit dans
un fichier est donnée par un pointeur
spécifique nommé seek pointer. Ce pointeur
est géré par le système d’exploitation et il
signale la position de traitement courante
dans le fichier c’est-à-dire l’emplacement de
l’octet auquel va s’effectuer la prochaine
opération.

121
Lecture et écriture en mode
caractère

int fgetc(FILE *fichier); lit un caractère et


retourne un entier. retourne EOF( constante
prédéfinie) si erreur ou fin de fichier.

int getc(FILE *fichier); elle a le même rôle que


la première.

int fputc(int entier, FILE *fichier); écrit la


valeur entier à la position courante du pointeur.
Le type de entier est converti de int en unsigned
char. La valeur de retour n’est autre que le
caractère lu ou EOF en cas d’erreur .

int putc(char c, FILE *fichier); écrit la valeur c


à la position courante du pointeur; retourne EOF
si erreur .

122
Exemple : lecture caractère par
caractère

#include<stdio.h>
#include <stdlib.h>
void main()
{
FILE * fichier; char c, nom[21];
printf ("entrer le nom du fichier");
gets(nom);
if ((fichier = fopen(nom, "r "))== NULL) {
printf( "erreur d’ouverture"); exit (-1);
}
while ((c = fgetc(fichier)) !=EOF)
printf("%c",c);
fclose(fichier);
}

123
Lecture et écriture en mode
chaine

Des fonctions analogues à gets et puts permettent de lire


et écrire une chaine de caractères dans un fichier.
char *fgets(char *chaîne, int lgmax, FILE *fichier); lit
lgmax-1 caractères à partir de la position courante et
les range dans chaine en ajoutant " \0 ". retourne NULL
si erreur ou fin de fichier.
fgets lit donc un certain nombre de caractères et les range
à l’emplacement référencé par chaine jusqu’à se
produise un des évènements suivants :
 Le caractère de saut de ligne « \n » a été lu
 lgmax-1 caractères ont déjà été lus
 La fin de fichier a été rencontrée

int fputs(char *chaîne, FILE *fichier); écrit la chaîne de


caractères à la position courante du pointeur.(\0 exclu).
retourne EOF si erreur .

124
Lecture et écriture formatées
La fonction fprintf écrit des données dans un fichier en les
formatant. Elle admet le prototype :
int fprintf(FILE *fichier, char *format, liste d’expressions);
Comme printf, fprintf accepte un nombre variable de
paramètres. Elle retourne comme résultat le nombre de
caractères écrits. Une valeur de retour négative (comme EOF)
indique une erreur.
fprintf(stdout, … ) et printf( …) sont équivalents.

La fonction fscanf lit des données dans un fichier en les


formatant. Elle admet le prototype :
int fscanf(FILE *fichier, char *format, liste d’adresses);
fscanf retourne le nombre de caractères correctement lus. La
valeur de retour EOF indique la fin de fichier ou une erreur.
fscanf(stdin, … ) et scanf( …) sont équivalents.

125
Ecriture dans un fichier texte
#include <stdio.h>
#include <stdlib.h>
main () {
char c; int x, y; char nomfich[21]; FILE *sortie;
printf("nom du fichier à créer :");
scanf("%20s", nomfich);
sortie = fopen (nomfich,"w");
if (!sortie ){
printf ("erreur d'ouverture"); exit(-1);
}
printf("donner les noms (* pour finir) et les coordonnées
des points");
do {
scanf(" %c%d%d", &nom, &x, &y);
if (c != ‘*’ )
fprintf(sortie, "%c%d%d",c, x, y);
} while (c != ‘*’ )
fclose(sortie);
}

126
Lecture à partir d’un fichier
texte
#include <stdio.h>
#include <stdlib.h>
main () {
char c; int x, y; char nomfich[21]; FILE *entree;
printf("nom du fichier à lister :"); scanf("%20s",
nomfich);
entree = fopen (nomfich,"r");
if (!entree ){
printf ("erreur d'ouverture"); exit(-1);
}
do{
fscanf(entree, " %c%d%d", &c, &x, &y);
if ( !feof(entree) )
printf( "%c%d%d",c, x, y);
} while (!feof(entree) );
printf("***fin de fichier***");
fclose(entree);
}

127
Instructions de lecture et
d’écriture

L’instruction d’écriture permet de transférer


plusieurs blocs consécutifs de même taille à partir
d’une adresse donnée vers le fichier. L’instruction
retourne le nombre de blocs effectivement écrits.
Syntaxe :
int fwrite( void *p, int taille_bloc, int nb_bloc,
FILE *fichier);
p est l’adresse d’un bloc
taille_bloc est la taille d’un bloc, en octets
nb_bloc est le nombre de blocs transférés au fichier
fichier : l’adresse de la structure décrivant le fichier
(le fichier logique)
Exemple
int n; FILE *fichier
...
fwrite(&n,sizeof(int), 1, fichier);

128
Instruction de lecture

L’instruction de lecture permet de transférer


plusieurs blocs consécutifs de même taille à
partir du fichier vers une adresse donnée.
En cas de succès fread retourne le nombre de
blocs (non pas octets) effectivement lus.
En cas d’erreur ou si c’est la fin de fichier
l’instruction retourne une valeur inférieur (en
général 0).
Syntaxe :
int fread(void *p, int taille_bloc, int
nb_bloc, FILE *fichier);
Exemple
int n; FILE *fichier
...
fread(&n,sizeof(int), 1, fichier);

129
La fonction feof

Syntaxe :
int feof(FILE *fichier);
Cette fonction est surtout utilisée en lecture.
Elle retourne 0 tant que la fin de fichier n’est pas
atteinte. elle est vrai lorsque le pointeur est
positionné au-delà de la fin du fichier c à d quand
la dernière lecture a échoué
feof n’est jamais vrai après l’ouverture (en lecture )
du fichier même lorsque celui-ci est vide. Il faut au
moins une lecture.
Exemple :
do
{ fread(&n, sizeof(int), 1, fichier);
if (!feof(fichier)) printf(« \n %d », n);
} while (!feof(fichier));

130
Fermeture d’un fichier

Toujours à la fin d’une session de


traitement d’un fichier, il faut fermer
le fichier. L’instruction de fermeture
est la suivante :
Syntaxe :
int fclose (FILE *fichier);

131
Création d’un fichier

#include <stdio.h>
#include <stdlib.h>
int main()
{char *nom = " exemple "; int n;
FILE *fichier = fopen(nom, "wb ");
if (fichier == NULL) {
printf ("erreur d'ouverture"); exit(-1);
}
do {
printf(" donner un entier ");
scanf(" %d ", &n);
fwrite(&n, sizeof(int), 1, fichier);
}while (n);
fclose(fichier);
return 0;
}

132
Liste séquentielle d’un fichier

#include <stdio.h>
#include <stdlib.h>
void main()
{char *nom = " exemple "; int n; FILE *fichier;
fichier = fopen(nom, " rb ");
if (!fichier ){
printf ("erreur d'ouverture"); exit(-1);
}
do
{ fread(&n, sizeof(int), 1, fichier);
if (!feof(fichier)) printf("\n %d " , n);
} while (!feof(fichier));

fclose(fichier);
}

133
Accès direct
Il est possible d’accéder à partir d’une position dans le fichier
vers une autre position à l’aide de la fonction fseek.
Cette fonction est utilisée surtout pour les fichiers binaires.
int fseek(FILE * f, int p, int position); déplace le pointeur
de p cases à partir de position.
Valeurs possibles pour position :
SEEK_SET (ou 0) : à partir du début du fichier
SEEK_CUR (ou 1) : à partir de la position courante du pointeur.
SEEK_END (ou 2) : en arrière à partir de la fin du fichier
Retourne 0 si le pointeur a pu être déplacé.
long int ftell(FILE *f); retourne le nombre d'octets du
début de fichier jusqu'à la position courante. Cette fonction
est habituellement utilisée avant fseek.

134
Exemple
#include <stdio.h>
#include <stdlib.h>
void main()
{char *nom = "exemple"; int n; long num;
FILE *fichier = fopen (nom," rb ");
if (!fichier ){
printf ("erreur d'ouverture"); exit(-1);
}
do {
printf(" rang dans le fichier : ");
scanf("%d" , &num);
if (num) {
fseek(fichier, (num -1) *sizeof(int), SEEK_SET);
fread(&n,sizeof(int),1,fichier);
printf ("%d \n" , n);
}
} while (num)
fclose(fichier);
}

135
ANNEXES

136
Principaux codes de conversion
- printf

c char: caractère affiché en clair


(convient aussi à short ou int compte
tenue des conversions systématiques)
d int (convient aussi à char)
u unsigned int (convient aussi à
unsigned char ou unsigned short)
ld long
lu unsigned long
f double ou float (compte tenu des
conversions systématiques float-
>double)écrit en notation décimale avec
six chiffres après la virgule
e double ou float écrit en notation
exponentielle
s chaîne de caractères

137
Principaux codes de conversion
- scanf

c char
d int
u unsigned int
hd short int
hu unsigned short int
ld long int
lu unsigned long int
f, e float écrit indifféremment dans l’une
des deux notations: décimale
(éventuellement sans point, c’est-à-dire
comme un entier) ou exponentielle(avec la
lettre e ou E)
lf , le double avec la même présentation
que ci-dessus
s chaîne de caractères

138
Les chaînes de caractères

une chaîne de caractères est un tableau


de caractères se terminant par le
caractère nul '\0'.
une chaîne de caractères peut être
définie à l'aide d'un pointeur:
char *chaine;
des affectations sont possibles comme :

chaine = "ceci est une chaîne";


toute opération valide sur les pointeurs
l’est sur chaine, comme l'instruction
chaine++;

139
Principales fonctions de la
bibliothèque string.h

 La fonction strlen(chaine) fournit la longueur de


chaine.
 La fonction strcat(ch1,ch2) recopie la seconde
chaine ch2 à la suite de la première ch1.
 La fonction strncat(ch1,ch2,lgmax) travaille de la
même façon que strcat en offrant un contrôle sur
le nombre de caractères concaténés.
 La fonction strcmp(ch1,ch2) compare deux
chaines et fournit une valeur positive entière si
ch1>ch2, nulle si ch1=ch2, négative si ch1<ch2.
 La fonction strncmp(ch1,ch2,lgmax) travaille
comme strcmp mais limite la comparaison au
nombre lgmax de caractères.

140
Principales fonctions de la
bibliothèque string.h

 Les fonctions stricmp(ch1,ch2) et


strnicmp(ch1,ch2,lgmax) travaillent comme strcmp et
strncmp mais sans tenir compte de la différence entre
majuscules et minuscules.
 La fonction strcpy(destin,source) recopie le chaine
source dans l’emplacement d’adresse detin.
 La fonction strncpy(destin,source,lgmax) limite la copie
au nombre de caractères lgmax.
 La fonction strchr(ch,caractere) recherche dans ch la
première position où apparaît le caractère mentionné.
 La fonction strrchr(ch,caractere) opère de même mais
en partant de la fin de ch.
 La fonction strstr(ch,ssch) recherche dans ch la
première occurrence de la sous chaine ssch.

141
Branchement multiple switch

switch (expression ){
case constante-1: liste d’instructions 1 ;
break;
case constante-2: liste d'instructions 2 ;
break;
...
case constante-n: liste d'instructions n;
break;
default: liste d'instructions ;
break;
}
Si la valeur de expression est égale à l'une des
constantes, la liste d'instructions correspondant est
exécutée. Sinon la liste d'instructions correspondant
à default est exécutée. L'instruction default est
facultative

142
Branchement non conditionnel
break

L'instruction break peut être employée à


l'intérieur de n'importe quelle boucle. Elle
permet d'interrompre le déroulement de la
boucle, et passe à la première instruction qui
suit la boucle. En cas de boucles imbriquées,
break fait sortir de la boucle la plus interne:

main()
{ int i;
for (i = 0; i < 5; i++) {
printf("i = %d\n",i);
if (i == 3) break;
}
printf(" i a la sortie de la boucle= %d\n",i);
}//i = 3

143
Branchement non conditionnel
continue

L'instruction continue permet de passer


directement au tour de boucle suivant, sans
exécuter le reste des instructions du corps de
la boucle.
main()
{int i;
for (i = 0; i < 5; i++) {
if (i == 3) continue;
printf(" i = %d",i);
}
printf(" \nvaleur de i a la sortie de la boucle =
%d",i);
}
//i = 0 i = 1 i = 2 i = 4
//valeur de i a la sortie de la boucle = 5

144

S-ar putea să vă placă și