Les e/s, les fichiers

Les E/S, Les Fichiers

Introduction

La bibliothèque <stdio.h> offre des fonctions de manipulation d'E/S (au-dessus de celles du système d'exploitation pour augmenter la portabilité des programmes).
Tout périphérique (clavier, écran, imprimante, ...) est manipulé comme un fichier.
Un fichier est une suite d'octets, accessible :
- de manière séquentielle (i.e. octets lus l'un après l'autre)
- par accès direct ou aléatoire (i.e. accès à un octet précis)

Il existe 3 fichiers pré-ouverts associés à la console (constantes dans <stdio.h>) :
- stdin (standard input) entrée standard (clavier), 0
- stdout (standard output) sortie standard (écran), 1
- stderr (standard error) sortie d'erreur standard (écran), 2
Redirections possibles à l'appel du programme : prog >out 2>err

Entrées/Sorties Bufferisées
Pour des raisons d'efficacité, l'accès à un fichier se fait par l'intermédiaire d'un buffer (ou mémoire tampon) pour réduire le nombre d'accès aux périphériques (disques, ...).
La taille de ce buffer est de 512 octets dans le cas d'un fichier physique.
Si le courant est coupé, on perd le contenu du buffer.
Il peut y avoir une désynchronisation entre le clavier et l'écran. En effet, les caractères tapés ne sont mis dans ce buffer que lorsqu'un retour chariot ou une fin de fichier est tapé (e.g. la fonction getchar).
On peut forcer le vidage du buffer avec la fonction

int fflush(FILE * descFichier);
fflush(stdin); // permet de vider le buffer d'entrée (ie ignore les caractères tapés)

autre solution

while (getchar() != '\n') ;

Tout fichier est identifié uniquement par sa position dans l'arborescence et par son nom. Dans un programme, il sera identifié par un descripteur de fichier i.e. un pointeur sur une structure FILE. Un élément de type TYPE* est appelé un flot de caractères, flux ou stream.
Une structure FILE indique la position courante dans le buffer, l'adresse du buffer, le nombre de caractères écrits ou restant à lire, mode d'accès (lecture, écriture, ...), ...

Il existe 2 types de fichiers :
- les fichiers textes : suite de caractères ASCII organisés en ligne (caractère linefeed à la fin de la ligne) finie par le caractère EOF (end of file). Les caractères de contrôle (retour à la ligne, ...) sont interprétés.
- les fichiers binaires : les caractères de contrôle ne sont pas interprétés.

Avant n'importe quelle opération sur un fichier, il faut l'ouvrir (fopen) (sauf stdin, stdout et stderr) puis faire les opérations:
- écriture/lecture caractère par caractère (getchar, putchar, getc, putc, fgetc, fputc, ungetc)
- écriture/lecture par ligne (gets, fgets, puts, fputs)
- écriture/lecture par enregistrements (fwrite, fread)
- écriture/lecture formatées (printf, fprintf, scanf, fscanf)
- déplacement dans le fichier (fseek, ftell)
Puis le fermer (fclose).

Ouverture d'un fichier

FILE * fopen(const char * nomFichierAOuvrir, const * char mode);

nomFichierAOuvrir : chaîne référençant dans l'arborescence le fichier à ouvrir.
mode : chaîne spécifiant le type d'ouverture. Par défaut le fichier est de type texte i.e. contenant des caractères ASCII. :
- "r" en lecture
- "w" en écriture (si le fichier existe, on écrase son contenu sinon on le crée)
- "a" en ajout (si le fichier existe, on ajoute à la fin du fichier, sinon on le crée).
Possibilités de suffixes :
- "b" fichier en mode binaire (données transférées sans interprétation)
- "+" ouvert en mode complémentaire du mode de base (e.g. "r+" ouvert en lecture/écriture).
Retourne un descripteur de fichier (égal à NULL s'il y a eu une erreur e.g. le fichier n'existe pas, problème de droits d'accès ou FOPEN_MAX fichiers sont déjà ouverts).

Fermeture de Fichier

int fclose(FILE * descFichierAFermer);

Détruit le lien entre le descripteur de fichier et le fichier physique. Provoque l'écriture des données du buffer en cas de fichier ouvert en mode écriture.
descFichierAFermer : descripteur du fichier à fermer.
Retourne EOF en cas d'erreur, sinon 0.
Exemple :

#include <stdio.h>
void main() {
  FILE * descFich;
  descFich = fopen("tp1/morpion.c", "r");
  if (descFich != NULL) {
  ....
  fclose(descFich);
  }
}

Lecture/Ecriture Caractère par Caractère

int fgetc(FILE * descFichierALire);

Retourne le caractère lu (de type unsigned char) dans le fichier associé ou EOF si on est à la fin du fichier ou s'il y a eu une erreur. Les 3 méthodes suivantes sont équivalentes :

int getchar(void);
int fgetc(stdin); 
int getc(stdin); 
int fputc(int caracAEcrire, FILE * descFichierOuEcrire);

Ecrit le caractère donné (de type unsigned char) dans le fichier. Retourne EOF en cas d'erreur ou la valeur du caractère écrit mise en entier. Les 3 méthodes suivantes sont équivalentes :

int putchar(int);
int fputc(int, stdout);
int putc(int, stdout);
int ungetc(int caracARemettre, FILE * descFichier);

Remet le caractère lu (sauf EOF) dans le flot de caractères. Retourne EOF en cas d'erreur ou la valeur du caractère remis.
Exemple :

#include <stdio.h>
/* Affiche le contenu du fichier passé en paramètre */
void main(int argc, char * argv * )
{
  if (argc > 1)
  {
  FILE * pFile = fopen(argv[1 * , "r"); /* ouverture du fichier */
  if (pFile != NULL)
 {
  char car;
  while ((car = fgetc(pFile)) != EOF) /* lecture des caractères */
 {
    fputc(car, stdout); /* ou putchar(car) ou putc(car, stdout) */
}
fclose(pFile); /* fermeture */
} 
else
  fprintf(stderr, "Impossible d'ouvrir le fichier %s", argv[1 * );
}
else
  fprintf(stderr, "Veuillez donner le nom du fichier à afficher\n");
}

Lecture/Ecriture par Lignes

char * fgets(char * ouMettreLesCaracteresLus, int tailleZoneOuMettreCarac, FILE * descFichierALire);

Lit les caractères (qui sont mis dans la zone mémoire pointée par ouMettreLesCaracteresLus) jusqu'au caractère de fin de ligne inclus, la fin de fichier ou jusqu'à (tailleZoneOuMettreCarac -1) caractères lus. fgets rajoute le caractère NULL, '\0', à la fin des caractères lus. Retourne le pointeur ouMettreLesCaracteresLus ou NULL si on est à la fin du fichier ou s'il y a eu une erreur.

Les 2 méthodes suivantes sont à peu près équivalentes :

fgets(char*, int, stdin); ~ char* gets(char* ouMettreLesCaracteresLus);

Attention : gets ne contrôle pas le nombre de caractères mis dans la zone de mémoire (d'où possibilité de débordement mémoire). De plus, le caractère de fin de ligne est transformé en caractère de fin de chaîne.

int fputs(const char * chaineAEcrire, FILE * descFichierOuEcrire);

Ecrit la chaîne donnée dans le flux. Retourne EOF en cas d'erreur sinon une valeur positive.

fputs(const char*, stdout);
int puts(const char * chaineAEcrire);

puts rajoute automatiquement le caractère de fin de ligne après l'écriture de la chaîne donnée (ce qui n'est pas le cas de fputs).
Exemple :

char chaine[MAX];
while (fgets(chaine, MAX, pFile) != NULL)
{
  fputs(chaine, stdout);
}

Lecture/Ecriture par Enregistrements

Pour les Structures ou des données de grande taille (fichier binaire).

size_t fread(void * zone, size_t taille, size_t nombre, FILE* stream);
size_t fread(void * zone, size_t taille, size_t nombre, FILE* stream);

zone : adresse de début de la zone de mémoire où mettre/lire les données (de taille au moins égale à taille*nombre)
taille : taille d'un enregistrement (en nombre d'octets)
nombre : nombre d'enregistrements à transférer
stream : descripteur de fichier ouvert en mode de transfert binaire
Retourne le nombre d'enregistrements lus/écrits
Fonctions permettant de transférer des données sans transcodage. Mais les fichiers produits ne sont pas portables. Ces fonctions sont utiles pour manipuler des données de grande taille ou des structures.

Lecture/Ecriture Formatées

int fscanf(FILE * ouLire, const char * chaineDeFormat, ...);
int fscanf(stdin, const char*, ...); &#8660; int scanf(const char*, ...);

Lit les données du flux en fonction des spécifications de format données dans chaineDeFormat et affecte les valeurs converties aux arguments donnés (qui sont des pointeurs, des adresses de variable). Retourne EOF en cas de fin de fichier atteinte ou s'il y a eu une erreur de conversion pour la première valeur, sinon elle retourne le nombre de valeurs converties et affectées.
La chaineDeFormat peut contenir:
- des caractères d'espace ou de tabulation (ces caractères sont vus comme
- des séparateurs ainsi que le retour chariot, saut de page ou fin de ligne),
- des caractères ordinaires (sauf %, espace ou tabulation) indiquant qu'ils devront être présents dans les données lues,
- des spécifications de format/conversion (ou séquences d'échappement) de la forme %[* * [taille][infoTaille] Type. A chaque spécification correspond à un argument (i.e une adresse de variable qui stockera la valeur lue et convertie).

Description des spécifications de conversion :
- * (optionnel) indique que la donnée lue ne sera pas affectée à un argument mais jetée.
- taille (optionnel) : nombre de caractères à lire, entier en base 10
- infoTaille (optionnel) : l (long), L (long) ou h (short)
- Type :
o d entier signé exprimé en base 10; int*
o i entier signé (en octal si commence par 0, en hexa si 0x ou 0X, en décimal sinon); int *
o o entier sous forme octale; int*
o u entier non signé exprimé en base 10; unsigned int*
o x entier sous forme hexa (précédé ou non de 0x ou 0X); int*
o e, f ou g nombre en virgule flottante (avec ou sans exposant E ou e); float*
o c caractère; char*
o s chaîne de caractère sans espace, tabulation ou retour chariot, avec le caractère '\0' à la fin; char* (tableau ou zone mémoire de taille suffisant grand pour contenir la chaîne entrée). Il faut utiliser fgets si on veut lire les espaces.
o p adresse (l'inverse de printf("%p", pTab);); void*
o n écrit dans l'argument le nombre de caractères lus
o [...* chaîne de caractères parmi un alphabet; char * (si [^...] caractères n'appartenant pas)

int fprintf(FILE * ouEcrire, const char * chaineDeFormat, ...);
int fprintf(stdout, const char*, ...); &#8660; int printf(const char*, ...);

Ecrit les données dans le flux en fonction des spécifications de format contenues dans chaineDeFormat. Retourne le nombre de caractères écrits ou une valeur négative en cas d'erreur.
La chaineDeFormat peut contenir:
- des caractères
- des spécifications de conversion de la forme %[indicateurs * [taille][.précision][infoTaille] Type
Description des spécifications de conversion :
- indicateurs (optionnels) :
o - indique que le résultat est à justifier à gauche
o + imprime le signe du nombre
o 0 complète le début du champs par des 0
o # spécifie un format de sortie diffèrent (pour un octal, ajoutera 0 devant; 0x pour un hexa, ...)
- taille (optionnel) : nombre minimal de caractères à écrire, entier en base 10
- précision (optionnel) : nombre max de caractères ou de chiffres à droite du point d'un nombre flottant ou le nombre min de chiffres pour un entier.
- infoTaille (optionnel) : l (long), L (long) ou h (short)
- type :
o d, i notation décimale signée; int
o o notation octale non signée (non précédée d'un 0) ; int
o u notation décimale non signée; unsigned int
o x, X notation hexa non signée(non précédée de 0x ou 0X); int
o f notation décimale de la forme [- * mmm.dddddd où le nombre de d est donné par la précision (par défaut, égal à 6); double
o e, E notation décimale de la forme [- * m.dddddde±xx ou [-]m.ddddddE±xx où le nombre de d est donné par la précision (par défaut, égal à 6); double
o g, G impression selon %e si l'exposant est inférieur à -4 ou supérieur ou égal à la précision, sinon comme %f; double
o c caractère (converti en unsigned char) ; int
o s chaîne de caractère imprimée jusqu'au '\0' ou selon la précision; char *
o p adresse; void*
o n le nombre de caractères écrits
Exemple :

#include <stdio.h>
#define MAX 100
void main(int argc, char * argv * )
{
  if (argc > 1)
  {
    FILE * pFile = fopen(argv[1 * , "w");
    if (pFile != NULL)
    {
      char nomProduit[MAX];
      int idProd;
      while (scanf("%d %s", &idProd, nomProduit) != EOF)
        fprintf(pFile, "|%6d|%20s|\n", idProd, nomProduit);
      fclose(pFile);
    }
    else
      fprintf(stderr, "Impossible d'ouvrir le fichier %s", argv[1 * );
  }
  else
    fprintf(stderr, "Veuillez donner le nom du fichier où on écrit\n");
}

Exemple de fichier produit

| 12| imprimante|
| 23| chaise|
| 2345| ordi|

Déplacement dans un Fichier

int fseek(FILE * descFichier, long offset, int pointDeDepart);

Change la position courante dans le stream.
offset : nombre d'octets dont on veut se déplacer (long positif ou négatif)
pointDeDepart :
- SEEK_SET (à partir du début du fichier)
- SEEK_CUR (à partir de la position courante)
- SEEK_END (à partir de la fin du fichier)
Retourne une valeur différente de 0 si erreur, 0 sinon.

int ftell(FILE * descFichier);

Retourne la valeur de la position courante (-1 en cas d'erreur). Utile pour mémoriser une position pour s'y repositionner ultérieurement.
Pour connaître la taille d'un fichier :

 fseek(pFich, 0L, SEEK_END);
longueur = ftell(pFich);

Autres Fonctions

Détruire un fichier

int remove(const char * nomFichierADetruire);

Retourne une valeur différente de 0 si échec.
Renommer un fichier

int rename(const char * nomFichierARenommer, const char*nouveauNom);

Fin de fichier

int feof(FILE * stream);

Test si la marque de fin de fichier a été lue (pour savoir si erreur ou EOF pour getc)

Gestion d'erreur

int ferror(FILE * stream); /* Retourne une valeur non nulle si une erreur s'est produite */
void perror(const char* chaine); /* Imprime chaîne et un message d'erreur correspondant à l'entier contenu dans errno. */
int sprintf(char *s, const char * format, ...);
int sscanf(char *s, const char * format, ...);

Comme printf et scanf mais pour un tableau.

Thebroyeur

Ce document intitulé « Les e/s, les fichiers » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous