1. Les différents types de fichiers.
On a déjà vu, dans la séance 2 de travaux pratiques,
qu'il existe deux catégories de fichiers, et ce indépendamment
de tout langage de programmation :
Pour savoir si un fichier est un fichier de texte ou un fichier binaire,
un moyen simple est de l'éditer à l'aide d'un éditeur
de texte (par exemple asedit) :
- Un fichier qui apparaît de manière lisible dans la fenêtre
de l'éditeur est un fichier de texte.
- Un fichier qui apparaît sous la forme de caractères illisibles
dans la fenêtre de l'éditeur est un fichier binaire.
Exemples :
- Un programme source (dans n'importe quel langage de programmation)
constitue un fichier de texte.
- Un programme objet ou un programme exécutable (correspondant
à un programme source écrit dans n'importe quel langage compilé)
constituent des fichiers binaires.
- La plupart des images (par exemples, les images aux formats bmp
ou jpg) constituent des fichiers binaires.
Remarque :
Certains fichiers sont partiellement de texte et partiellement binaires.
Par exemple, une image au format ppm (cf. la séance
6 de travaux pratiques) a un en-tête de type texte, mais le reste
du fichier est de type binaire.
Tout fichier, de texte ou binaire, est composé d'octets, mais
une même information est codée différemment dans un
fichier de texte ou dans un fichier binaire.
Exemple :
- 00110010 00110100 dans un fichier de texte : un octet
pour le code ASCII du caractère 2, qui vaut 50,
et un octet pour le code ASCII du caractère 4, qui
vaut 52.
- 00000000 00000000 00000000 00011000 dans un fichier
binaire : quatre octets sont nécessaires pour stocker
un entier de type int, sur marine. On voit pourquoi ce
type de codage est moins "universel" que le précédent.
Par conséquent :
- Si on souhaite lire une valeur entière dans un fichier de texte,
et l'affecter à une variable entière a de
type int, il faudra utiliser la fonction fscanf,
déjà présentée lors de la deuxième séance
de travaux pratiques. En revanche, si on souhaite faire de même,
mais cette fois-ci dans un fichier binaire, il suffira de recopier un bloc
de quatre octets du fichier vers la mémoire, à l'adresse
de a, à l'aide de la fonction fread,
qui sera décrite dans le paragraphe 3.
- Si on souhaite écrire dans un fichier de texte la valeur d'une
variable entière a de type int,
il faudra utiliser la fonction fprintf, déjà
présentée lors de la deuxième séance de travaux
pratiques. En revanche, si on souhaite faire de même, mais cette
fois-ci dans un fichier binaire, il suffira de recopier un bloc de quatre
octets de la mémoire, à l'adresse de a, vers
le fichier, à l'aide de la fonction fwrite, qui
sera décrite dans le paragraphe 4.
Remarque :
Rien n'interdit a priori d'utiliser les fonctions fread
et fwrite pour lire ou écrire dans un fichier de
texte, ni d'utiliser les fonctions fscanf et fprintf
pour lire ou écrire dans un fichier binaire, mais de telles utilisations
peuvent être sources d'erreurs difficiles à corriger.
Pour effectuer des lectures dans un fichier de texte à l'aide de la fonction
fscanf, on ouvrira ce fichier sous le mode "rt".
Pour effectuer des écritures dans un fichier de texte à l'aide de
la fonction fprintf, on ouvrira ce fichier sous les modes
"wt" (c'est-à-dire qu'on écrasera l'ancien contenu de ce fichier)
ou "at" (c'est-à-dire que les écritures seront concaténées
à l'ancien contenu de ce fichier). Pour effectuer des lectures dans un fichier
binaire à l'aide de la fonction fread, on ouvrira ce fichier
sous le mode "rb". Enfin, pour effectuer des
écritures dans un fichier binaire à l'aide de la fonction fwrite,
on ouvrira ce fichier sous les modes "wb" (écriture en début de fichier) ou "ab" (écriture en fin de fichier). Là
encore, ces règles peuvent être transgressées, mais
il peut s'ensuivre des erreurs difficiles à corriger.
Pour effectuer des lectures dans un fichier image au format ppm, qui comporte
une partie de texte et une partie binaire, suivant qu'on manipule l'une
ou l'autre partie, on utilisera les fonctions fscanf ou
fread, et donc on ouvrira le fichier sous le mode "rt"
ou sous le mode "rb" (cf. la séance
6 de travaux pratiques).
2. Avantages comparés des différents types de fichiers.
On peut se demander la raison pour laquelle il existe plusieurs types de fichiers. Plutôt que de disserter
sur leur nécessaire coexistence, on se contentera ici
de citer quelques avantages comparés de ces deux types de fichiers dans le cas de valeurs numériques
(comme par exemple les images), puisque des valeurs numériques peuvent être stockées, a priori,
sous forme de caractères (les chiffres plus le caractère .) ou sous forme binaire :
- Pour stocker une même information numérique, un fichier de texte et un fichier binaire n'ont pas la
même taille, comme cela a déjà été souligné dans l'exemple ci-dessus.
Par exemple, un ensemble de 1000 entiers comportant chacun exactement 9 chiffres
requiert une place de 10000 octets s'il s'agit d'un fichier de texte (les 9000
chiffres plus 1000 caractères séparateurs), alors qu'il requiert une place de 4000
octets
sur marine, car il faut utiliser des entiers de type long, codés sur 4
octets. De manière générale, bien que cela ne soit pas une règle, un fichier binaire permet de stocker
des valeurs numériques sur moins de place qu'un fichier de texte.
- La lecture ou l'écriture de données numériques dans des fichiers de texte nécessite une conversion, car
les valeurs numériques en mémoire centrale sont codées sous forme binaire. En ce sens, les fonctions
fscanf et fprintf sont forcément plus lentes que leurs homologues
fread et fwrite, et si le nombre de lectures ou d'écritures s'avère important,
il est clair que l'utilisation de fichiers binaires est préférable.
- Au vu des deux points qui viennent d'être soulignés, on peut se demander en quoi il est intéressant
de stocker des données numériques sous la forme d'un fichier de texte. Effectivement, la plupart des images
sont stockées dans des fichiers binaires. Cependant, les fichiers de texte permettent une meilleure visualisation
des données et une plus grande portabilité.
3. Lecture dans un fichier binaire : fonction
fread.
La lecture d'un bloc d'octets dans un fichier binaire s'effectue à
l'aide de la fonction fread, d'en-tête :
size_t fread(void *pt,size_t taille,size_t nb_infos,FILE *id_fich)
où :
- size_t est un type identique au type unsigned
int ("entier non signé").
- pt est un pointeur non typé sur la zone mémoire
recevant les informations lues (cette catégorie de pointeurs, également appelés
"pointeurs génériques", sera détaillée dans la neuvième séance de travaux dirigés).
- taille est la taille de chaque information lue (en
nombre d'octets).
- nb_infos est le nombre d'informations à lire.
- id_fich est l'identificateur du fichier, de type FILE
*, sur lequel on opère la lecture. Cet identificateur ne
doit pas être confondu avec le nom du fichier, et doit préliminairement
avoir reçu une adresse, grâce à la fonction fopen
(cf. la séance 2 de travaux pratiques).
La fonction fread renvoie le nombre d'informations effectivement
lues. Ce nombre d'informations peut être inférieur au nombre
requis, dans le cas où la fin du fichier est atteinte.
4. Écriture dans un fichier binaire : fonction
fwrite.
L'écriture d'un bloc d'octets dans un fichier binaire s'effectue
à l'aide de la fonction fwrite, d'en-tête
:
size_t fwrite(void *pt,size_t taille,size_t nb_infos,FILE *id_fich)
La fonction fwrite renvoie le nombre d'informations
effectivement écrites. Ce nombre d'informations peut être
inférieur au nombre requis, dans le cas où l'écriture est
effectuée sur un disque saturé.
Exemple :
#include <stdio.h>
#define N 256
int main(void)
{
FILE *f1,*f2; /* Identificateurs
de fichiers. */
char mat[N][N];
if ((f1=fopen("image.brt","rb"))==NULL)
![](../ICONE/indent.gif)
printf("Fichier original introuvable.\n");
else
{
![](../ICONE/indent.gif)
if (fread(mat,1,N*N,f1)<N*N)
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
printf("Manque de données dans le fichier original.\n");
![](../ICONE/indent.gif)
fclose(f1);
![](../ICONE/indent.gif)
}
![](../ICONE/indent.gif) else
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
if
((f2=fopen("image_copie.brt","wb"))==NULL)
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
printf("Création du fichier copie impossible.\n");
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
fclose(f1);
![](../ICONE/indent.gif)
}
![](../ICONE/indent.gif)
else
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
if (fwrite(mat,1,N*N,f2)<N*N)
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
printf("Erreur à l'écriture.\n");
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
fclose(f1);
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
fclose(f2);
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
}
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
else
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
{
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
printf("Copie terminée. Tout s'est bien passé.\n");
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
fclose(f1);
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
fclose(f2);
![](../ICONE/indent.gif)
![](../ICONE/indent.gif)
}
![](../ICONE/indent.gif)
}
![](../ICONE/indent.gif)
}
}
return(0);
} |
5. Positionnement dans un fichier : fonction
fseek.
Il peut être utile d'accéder à un fichier de manière
directe ("par accès direct"), pour y effectuer des lectures
ou des écritures, mais il faut alors gérer explicitement
le pointeur d'octets qui donne la "position courante" dans le
fichier. Cela est possible, grâce à la fonction fseek,
d'en-tête :
int fseek(FILE *id_fich,long decalage,int depart)
où :
- id_fich est l'identificateur du fichier dans lequel
on cherche à se positionner.
- decalage est le nombre d'octets dont on doit se décaler
à partir de la position donnée par depart.
- depart peut être égal à :
- SEEK_SET ou 0 (début de fichier).
- SEEK_CUR ou 1 (position courante dans
le fichier).
- SEEK_END ou 2 (fin de fichier).
La fonction fseek renvoie 0 si tout s'est bien passé
et -1 sinon, en particulier dans le cas où on essaierait de
revenir en arrière avant le début d'un fichier. Néanmoins, lorsqu'on
essaie d'avancer au-delà de la fin d'un fichier, la valeur retournée par fseek
vaut 0 et non pas -1, comme on pourrait s'y
attendre (tout se passe comme si on "piétinait" à la fin du fichier).
Remarque :
Les fonctions fscanf, fprintf, fread
et fwrite mettent à jour la valeur de la position
courante. Après la lecture ou l'écriture dans un fichier
d'une certaine quantité d'octets, situés entre les adresses
adr1 (comprise) et adr2 (non comprise),
la position courante aura pour nouvelle valeur l'adresse adr2.
Ces quatre fonctions d'entrée/sortie sur des fichiers ne fonctionnent
donc qu'en accès séquentiel, et c'est seulement la fonction
fseek qui autorise un accès direct.
6. Test de fin de fichier : fonction feof.
La fonction feof, d'en-tête :
renvoie une valeur non nulle si la fin du fichier (d'identificateur
id_fich) est atteinte, et 0 sinon.
Remarque :
Il est conseillé de limiter l'utilisation de la fonction feof au minimum,
car le comportement de cette fonction est difficilement prévisible lorsqu'on l'applique
sur un fichier dans lequel aucune lecture n'a encore été faite, et cela peut provoquer,
par exemple, des boucles infinies difficiles à interpréter.
Par conséquent, lorsqu'on lit un fichier de texte (à l'aide de la fonction fscanf),
il est conseillé d'utiliser feof plutôt que la valeur retournée par
fscanf pour détecter la fin du fichier,
mais lorsqu'on lit un fichier binaire, il vaut mieux utiliser la valeur retournée par
fread plutôt que feof pour détecter la fin du fichier.
Attention :
Il ne faut pas confondre la fonction feof avec la constante symbolique
EOF, de valeur -1, qui est parfois retournée par certaines
fonctions prédéfinies comme, par exemple, fscanf. La constante EOF
n'est pas un caractère spécial, qui se trouverait systématiquement situé en fin de fichier
(un tel caractère "terminateur de fichier" n'existe pas). La constante EOF
ne correspond d'ailleurs pas à un caractère, puisqu'aucun code ASCII n'est négatif.
7. Sortie d'un programme : fonction exit.
L'arrêt de l'exécution d'un programme s'effectue à
l'aide d'un appel à la fonction exit, d'en-tête :
Par convention, il est conseillé de retourner la valeur 0
lorsque tout s'est bien passé, et une valeur entière non nulle lorsqu'il
y a eu un problème. Cette valeur s'appelle le "code de retour".
Il ne faut pas confondre l'appel exit(code_retour);
avec les instructions break; (cette
instruction permet de sortir d'un bloc), return(valeur);
ou return valeur; (ces instructions, placées
dans une fonction, permettent de sortir de cette fonction, en renvoyant
la valeur valeur non forcément entière, sans arrêt de l'exécution
du programme). En revanche, placées dans le programme principal
main, les instructions return(code_retour);
ou return code_retour;
provoquent l'arrêt du programme, ainsi que le renvoi
de l'entier code_retour (sauf dans le cas où la fonction
main est appelée récursivement).
Remarque :
Alors que return est un opérateur, ce qui explique
qu'on puisse écrire indifféremment return(valeur); ou return valeur;
on doit forcément écrire exit(valeur); car exit est une fonction.
Exemple :
On peut maintenant compléter l'exemple du paragraphe 4, en codant
les différents types d'erreur, par exemple de la manière
suivante (les rajouts par rapport à l'exemple du paragraphe 4 apparaissent en rouge) :
#include <stdio.h>
#include <stdlib.h>
#define N 256
int main(void)
{
FILE *f1,*f2; /* Identificateurs
de fichiers. */
char mat[N][N];
if ((f1=fopen("image.brt","rb"))==NULL)
{
![](../ICONE/indent.gif) printf("Fichier
original introuvable.\n");
![](../ICONE/indent.gif) exit(1);
}
else
{
![](../ICONE/indent.gif) if
(fread(mat,1,N*N,f1)<N*N)
![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) printf("Manque
de données dans le fichier original.\n");
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f1);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) exit(2);
![](../ICONE/indent.gif) }
![](../ICONE/indent.gif) else
![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) if
((f2=fopen("image_copie.brt","wb"))==NULL)
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) printf("Création
du fichier copie impossible.\n");
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f1);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) exit(3);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) }
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) else
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) if
(fwrite(mat,1,N*N,f2)<N*N)
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) printf("Erreur à l'écriture.\n");
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f1);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f2);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) exit(4);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) }
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) else
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) {
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) printf("Copie
terminée. Tout s'est bien passé.\n");
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f1);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) fclose(f2);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) exit(0);
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) ![](../ICONE/indent.gif) }
![](../ICONE/indent.gif) ![](../ICONE/indent.gif) }
![](../ICONE/indent.gif) }
}
} |
Remarques :
- À cause des appels à la fonction exit,
les quatre mots-clés else sont ici facultatifs.
- Pour pouvoir utiliser la fonction exit, il est nécessaire de faire apparaître la directive d'inclusion du fichier stdlib.h
8. Entrée standard et sorties standard d'une commande.
Un programme exécutable est l'équivalent d'une commande UNIX (un certain nombre de commandes UNIX ont
déjà été énumérées lors de la première séance de travaux pratiques).
Les noms stdin, stdout, stderr
désignent respectivement "l'entrée standard",
la "sortie standard" et la "sortie standard
des erreurs" d'une commande. Par défaut, l'entrée standard est associée au clavier, la sortie standard
et la sortie standard des erreurs sont associées à l'écran. Il n'y a donc pas besoin d'utiliser la
fonction fopen pour pouvoir lire des caractères tapés au clavier ou afficher des
caractères à l'écran.
À l'intérieur d'un programme, pour effectuer une lecture sur l'entrée standard, on écrit
(en supposant que a est une variable de type int) :
scanf("%d",&a);
ou
fscanf(stdin,"%d",&a);
À l'intérieur d'un programme, pour effectuer une écriture sur la sortie standard, on écrit :
printf("chaîne",expr);
ou
fprintf(stdout,"chaîne",expr);
À l'intérieur d'un programme, pour effectuer une écriture sur la sortie standard des erreurs, on écrit :
fprintf(stderr,"chaîne",expr);
On peut "rediriger" l'entrée
standard, la sortie standard ou la sortie standard des erreurs d'une commande, et donc en particulier d'un programme,
sur des fichiers. La redirection de l'entrée standard de la commande nom_commande
sur le fichier de nom f_entree se fait grâce à
la syntaxe suivante, au moment du lancement de cette commande :
> nom_commande < f_entree![](../ICONE/indent.gif) |
La redirection de la sortie standard de la commande nom_commande
sur le fichier de nom f_sortie se fait grâce à
la syntaxe suivante :
> nom_commande > f_sortie![](../ICONE/indent.gif) |
La redirection de la sortie standard des erreurs de la commande nom_commande
sur le fichier de nom f_erreurs se fait grâce à
une syntaxe qui dépend du shell utilisé. Avec le shell par défaut sur marine, qui est
tcsh, c'est :
> nom_commande >& f_erreurs![](../ICONE/indent.gif) |
En revanche, avec le "shell de Bourne", qui seul sera étudié dans le cadre du module 4,
la syntaxe est la suivante (remarquer le nouveau prompt, caractéristique du shell de Bourne) :
$ nom_commande 2> f_erreurs![](../ICONE/indent.gif) |
Remarque :
Il est important de noter que si l'entrée et les sorties standard peuvent être
redirigées vers des fichiers quelconques, le mode d'accès à ces fichiers ne peut être que
t (mode "texte").
9. Exercice 1.
Écrire un programme permettant de recopier le fichier fich1
dans un fichier fich2, par paquets de 512
octets (sauf, le cas échéant, pour le dernier paquet).
Exemple d'appel du programme exécutable
:
> recopie < fich1 > fich2![](../ICONE/indent.gif) |
On codera les différents types d'erreurs de la façon suivante :
- exit(0); si tout fonctionne bien.
- exit(1); en cas d'erreur à l'écriture.
Il est conseillé d'utiliser l'algorithme suivant, écrit
en pseudo-langage, dans lequel les codes de retour ne sont pas précisés
:
tant qu'on peut lire 512 octets :
{
écrire 512 octets
;
si écriture impossible,
alors sortir ;
}
s'il
n'y a plus d'octets à écrire, alors sortir ;
sinon
:
{
écrire
les octets restants ;
si
écriture impossible, alors sortir ;
sinon,
sortir ;
} |
Dans chaque cas, on fera afficher un message
explicite sur la sortie standard des erreurs.
10. Exercice 2.
1) Écrire une fonction paquet, permettant de lire
n octets dans un fichier d'identificateur id_fich
à partir d'une position pos
(on suppose que ce fichier
a déjà été ouvert en lecture dans le programme principal),
et de les stocker dans un
tableau tab :
int paquet(char tab[],int n,long pos,FILE *id_fich)
On sortira de cette fonction
avec :
- return(0); si tout fonctionne bien.
- return(1); en cas d'erreur en lecture.
2) Écrire le programme principal appelant cette fonction.
Ces pages ont été réalisées
par A. Crouzil, J.D. Durou et Ph. Joly.
Pour tout commentaire, envoyer un mail à
crouzil@irit.fr, à durou@irit.fr ou à Philippe.Joly@irit.fr.