Le langage C


précédentsommairesuivant

VII. Entrées-sorties

Strictement parlant, les opérations d'entrée-sortie ne font pas partie du langage C; elles sont effectuées par des fonctions de la bibliothèque que chaque système offre avec le compilateur. Or, C est né et a vécu longtemps en milieu UNIX avant d'être transporté ailleurs ; la bibliothèque UNIX a donc souvent été prise pour référence, semant quelque confusion car la réalisation de certaines fonctions d'UNIX est inutile, voire impossible sur certains systèmes. La norme ANSI y a mis bon ordre ; en principe la liste des fonctions standard est maintenant clairement établie et un programme qui n'utilise que ces fonctions doit pouvoir être transporté d'un système à un autre sans modification. Rien n'empêche que telle ou telle réalisation particulière du langage propose des fonctions additionnelles ; il faudra alors vous référer à la documentation spécifique.

VII-A. Flots

Quelles que soient les particularités du système d'exploitation sous-jacent, les fonctions de la bibliothèque standard des entrées-sorties font en sorte que les fichiers soient vus par le programmeur comme des flots, c'est- à-dire des suites d'octets qui représentent des données selon une des deux modalités suivantes :

  • soit le fichier contient les caractères qui constituent l'expression écrite des données en question d'après une certaine syntaxe. Ces caractères sont eux-mêmes représentés selon une convention largement répandue, presque toujours le code ASCII. De tels fichiers sont appelés fichiers de texte. Leur principale qualité est d'être exploitables par un logiciel, un système ou un équipement différents de celui qui les a produits ; en particulier, ils peuvent être affichés, édités, imprimés, etc. ;
  • soit le fichier contient des données enregistrées sous une forme qui est la copie exacte de leur codage dans la mémoire de l'ordinateur. On l'appelle alors fichier binaire. Les opérations de lecture ou d'écriture sur de tels fichiers sont très rapides, car elles ne requièrent pas un travail d'analyse ou de synthèse de l'expression écrite des données. En revanche, les fichiers binaires ne sont pas éditables ou imprimables ; ils ne sont pas censés être transportables d'un logiciel à un autre, encore moins d'un système à un autre.

Du point de vue du langage C (il n'en est peut être pas de même pour le système sous-jacent) être de texte ou binaire n'est pas une propriété d'un fichier, mais de la manière dont il est traité par un programme. Ainsi la distinction entre ces deux types de fichiers ne se fait pas lors de la déclaration ou de l'ouverture 51 du fichier, mais par le choix des fonctions de lecture ou d'écriture qui sont utilisées : certaines fonctions (fgets et fputs, lecture et écriture d'une ligne, et fscanf et fprintf, lecture et écriture de données formatées) n'ont de sens que sur un flot de texte. D'autres fonctions (fread, fwrite) effectuent la lecture et l'écriture de paquets d'octets sans interprétation aucune, et correspondent donc plutôt aux flots binaires 52.

Les flots binaires sont de simples suites d'octets. Les flots de texte ont une structure légèrement plus riche, puisqu'ils sont censés être organisés comme des suites de lignes. Chaque ligne est faite d'un nombre quelconque de caractères distincts de '\n' et se termine par le caractère '\n'. Même si sur un système particulier les fichiers de texte ne sont pas ainsi organisés, ou si le caractère qui indique la fin d'une ligne est différent, la bibliothèque standard fera en sorte qu'ils puissent être vus de cette manière par le programmeur 53.

Tampons. Un fichier est dit tamponné 54 lorsque les transferts physiques d'octets entre le fichier et un programme qui le lit ou l'écrit se font par paquets de taille fixe, la taille du tampon, même si les échanges logiques sont faits par paquets de taille variable ou même octet par octet. Par défaut un flot est toujours associé à un tampon de taille prédéterminée, mais le programmeur peut, à l'aide de la fonction setvbuf (cf. section VII.A.1), modifier la taille de ce dernier ou même le supprimer complètement.

Ce mode de gestion des fichiers réduit le nombre d'opérations physiques d'entrée - sortie (beaucoup plus lentes, quelles que soient les performances des disques durs actuels, que les transferts d'information purement internes) mais introduit un décalage dans leur chronologie. Ce que le programmeur croit être une opération d'écriture dans un fichier n'est en réalité qu'une « opération logique », c'est-à-dire une recopie dans un tampon situé en mémoire, dont le contenu sera transféré ultérieurement vers le fichier. Par conséquent, il est difficile ou impossible de savoir dans quel état se trouve le fichier à un moment donné ; de plus, si le programme vient à subir une terminaison anormale, une partie des informations logiquement écrites dans le fichier seront perdues, parce qu'elles n'ont jamais été physiquement transférées 55.

51Il y a quand-même un point de détail qu'il fait fixer lors de l'ouverture (voyez la fonction fopen) : y a-t-il lieu de guetter les marques de fins-de-ligne pour les transformer dans le caractère 'nn' et réciproquement ?

52Conséquence : sauf si le système sous-jacent l'interdit, un fichier écrit comme un °ot de texte peut être lu comme un °ot binaire. Cela revient à ignorer le fait que les octets dont il se compose représentent des données d'un ordre supérieur. La démarche inverse, lire comme un °ot de texte un fichier binaire, n'a pas de signification et peut provoquer des erreurs fatales (les fins de ligne seront absentes ou incomprises, le tampon débordera, etc.).

53En particulier, le couple (CR,LF) utilisé par certains systèmes est traduit en lecture par l'unique caractère 'nn'. Inversement, sur de tels systèmes, l'écriture « logique » du caractère 'nn' se traduit par l'écriture effective du couple (CR,LF).

54En bon franglais, on dit plutôt « bufferisé ».

55S'il existe une probabilité non négligeable pour qu'un programme ait une fin anormale (par exemple à cause de conditions d'exploitation difficiles) il est prudent de programmer des appels réguliers de la fonction fflush (cf. section VII.A.1)

VII-A-1. Fonctions générales sur les flots

Les flots sont représentés dans les programmes par des variables de type FILE *. La structure FILE est définie dans le fichier <stdio.h>. Pour utiliser un ou plusieurs flots il faut donc écrire en tête de son programme la directive

 
Sélectionnez

#include <stdio.h>

puis une déclaration de la forme

 
Sélectionnez

FILE *fichier;

Un fichier est donc représenté en C par une variable d'un type pointeur. Par conséquent, sa déclaration n'autorise aucune des opérations propres aux fichiers, aussi longtemps qu'on n'aura pas fait le nécessaire (voyez la fonction fopen ci-après) pour qu'il pointe sur une structure légitimement allouée et qui constitue la description d'un fichier ouvert.

Parmi les principales fonctions qui effectuent les opérations générales (ouverture, fermeture, etc.) sur les flots nous avons :

 
Sélectionnez

FILE *fopen(const char *nom, const char *mode)

Cette fonction ouvre le fichier dont le nom est indiqué par la chaine nom et rend un pointeur sur le flot correspondant, ou NULL si l'opération a échoué (fichier absent, etc.). Le nom doit être correct pour le système d'exploitation sous-jacent ; cela ne regarde pas le langage C.

Les valeurs permises pour mode sont :

  • "r" (read) ouverture d'un fichier. Le fichier doit exister ; son contenu n'est pas détruit. Le descripteur du flot créé est positionné en lecture et au début du fichier. En principe, seules les opérations de lecture sont permises sur ce flot
  • "r+" comme "r", mais les opérations d'écriture sont permises aussi
  • "w" (write) création d'un fichier. Le fichier peut exister ou non ; s'il existe, son contenu est entièrement effacé. Le descripteur du flot créé est positionné en écriture et au début du fichier (qui est vide). En principe, seules les opérations d'écriture sont permises sur ce flot
  • "w+" comme "w", mais les opérations de lecture sont permises aussi
  • "a" (append) allongement d'un fichier. Le fichier existe ou non ; s'il existe, son contenu n'est pas effacé. Le descripteur du flot créé est positionné en écriture et à la fin du fichier. Seules les opérations d'écriture sont permises
  • "a+" comme "a", mais les opérations de lecture sont permises aussi
  • "rb", "r+b", "wb", "w+b", "ab", "a+b" : si on envisage d'utiliser le fichier en mode binaire il faut ajouter la lettre b au mode ("r+b", "w+b" et "a+b" peuvent se noter aussi "rb+", "wb+" et "ab+")

Remarque 1. La seule différence que la lettre b dans le mode introduit dans le fonctionnement de toutes les fonctions d'entrée-sortie est la suivante : si le fichier est « de texte » (i.e. si la lettre b ne figure pas dans le mode) alors :

  • en lecture, chaque occurrence d'une marque de fin de ligne est détectée et, si cette marque n'est pas le caractère '\n' lui-même, remplacée par l'unique caractère '\n',
  • toute demande d'écriture du caractère '\n' produit en fait l'envoi effectif au fichier de la marque de fin de ligne requise par le système sous-jacent.

Il n'est donc pas exclu que sur un système d'exploitation donné l'ajout de la lettre b au mode soit sans effet. C'est notamment le cas d'UNIX, ou les fins de ligne sont indiquées par le caractère '\n'.

Remarque 2. Sur l'intérêt qu'il peut y avoir à effectuer des lectures et des écritures sur le même fichier, voir les remarques faites à la section 7.4.3 à propos des fichiers en accès relatif.

 
Sélectionnez

int fflush(FILE *flot)

Dans le cas d'un flot de sortie, cette fonction provoque l'écriture « physique » immédiate du tampon en cours de remplissage. Elle rend EOF en cas d'erreur, zéro dans les autres cas.

D'après la norme officielle du langage C, l'effet de fflush sur un flot qui n'est pas un flot de sortie est indéfini. Mais pour la plupart des bibliothèques actuelles, l'appel de cette fonction sur un flot d'entrée supprime les caractères disponibles dans le tampon. Par exemple, dans le cas fréquent ou l'entrée standard correspond au clavier, l'appel fflush(stdin) fait disparaitre tous les caractères déjà tapés mais pas encore lus par le programme.

Remarque. Si le fichier physique qui correspond au flot indiqué est un organe interactif, par exemple l'écran d'un poste de travail, alors la fonction fflush est implicitement appelée dans deux circonstances très fréquentes :

  • l'écriture du caractère '\n' qui produit l'émission d'une marque de fin de ligne et la vidange effective du tampon,
  • le début d'une opération de lecture sur l'unité d'entrée associée (les organes d'entrée-sortie interactifs forment généralement des couples) ; ainsi, par exemple, une lecture au clavier provoque la vidange du tampon d'écriture à l'écran. Cela permet qu'une question soit effectivement affichée avant que l'utilisateur ne doive taper la réponse correspondante.
 
Sélectionnez

int fclose(FILE *flot)

Cette fonction ferme le flot indiqué, produisant l'écriture physique des tampons, la restitution de l'espace alloué pour le tampon et les descripteurs du flot, etc. Elle rend zéro en cas de succès, une autre valeur en cas d'erreur.

A la suite d'un appel comme fclose(flot) l'emploi de la variable flot est illégitime. Oublier de fermer, par un appel de fclose, un fichier qui a été ouvert en lecture n'est en général pas une erreur importante. Mais oublier de fermer un fichier qui a été ouvert en écriture (c'est-à-dire un fichier qui vient d'être créé) peut être fatal pour l'information qui y a été écrite, car

  • le contenu du tampon d'écriture en cours de formation lors de la terminaison du programme ne sera probablement pas sauvegardé dans le fichier ;
  • dans certains systèmes, les fichiers nouvellement créés ne subissent les opérations qui les rendent connus du système d'exploitation (comme l'inclusion de leur nom dans un répertoire) qu'au moment de leur fermeture. Un fichier qui n'a jamais été fermé a donc une existence très précaire.
 
Sélectionnez

int feof(FILE *flot)

Cette fonction n'a d'intérêt que si flot désigne un fichier en lecture. Elle renvoie une valeur non nulle si et seulement si l'indicateur de fin de fichier du flot indiqué est vrai. Cela se produit non pas lorsque le fichier est positionné sur sa fin mais lorsqu'il est placé au-delà de sa fin.

Autrement dit, feof(fic) devient vrai immédiatement après qu'une lecture sur fic ait échoué parce qu'il n'y avait plus [assez] d'informations dans fic pour la satisfaire.

En particulier, feof n'est jamais vrai après l'ouverture (en lecture) du fichier, même lorsque ce dernier est vide. Il faut au moins une [tentative de] lecture pour, éventuellement, le rendre vrai.

Cette spécification de la fonction feof fonde le schéma suivant, de lecture séquentielle d'un fichier :

 
Sélectionnez

FILE *fichier;
...
fichier = fopen(nom, "rb");
/* vérification du succès de l'ouverture de fichier */
...
/* lecture de données sur fichier */
while ( ! feof(fichier)) {
	traitement des données lues
	lecture de données sur fichier
}
...
int ferror(FILE *flot)

Cette fonction renvoie une valeur non nulle si et seulement si l'indicateur d'erreur du flot indiqué est « vrai ». Elle doit être appelée immédiatement après une entrée-sortie sur ce flot pour savoir si l'opération en question a échoué. La variable globale entière errno (déclarée dans <errno.h>) contient alors un code renseignant sur la nature de l'erreur.

 
Sélectionnez

FILE *tmpfile(void)

Cette fonction crée un fichier temporaire en mode "w+b", sans nom externe, qui sera automatiquement détruit à la terminaison du programme. Elle renvoie le flot associé ou NULL en cas d'erreur.

 
Sélectionnez

int setvbuf(FILE *flot, char *tampon, int mode, size t taille)

Cette fonction redéfinit le tampon associé à un flot ouvert. Elle doit être appelée après l'ouverture du flot mais avant toute lecture ou écriture. L'argument mode doit être une des trois constantes suivantes, définies dans <stdio.h> :

  • IOFBF Tampon de taille fixe. L'écriture ou la lecture physique a lieu lorsque le tampon contient taille caractères.
  • IOLBF Tampon égal à une ligne. L'écriture ou la lecture physique a lieu lors de la rencontre du caractère 'nn' qui détermine la fin des lignes.
  • IONBF Pas de tampon. Une écriture ou une lecture physique a lieu à chaque lecture ou écriture logique. L'argument taille indique la taille voulue pour le tampon. L'argument tampon, s'il n'est pas NULL, est censé pointer un espace (de taille au moins égale à taille) qui sera utilisé comme tampon. Si tampon est NULL, alors la fonction alloue un espace propre.

VII-A-2. Les unités standard d'entrée-sortie

Sans que le programmeur n'ait à prendre aucune disposition particulière, l'exécution d'un programme commence avec trois flots de texte automatiquement ouverts par le système. Ils sont connectés (on dit affectés) aux organes d'entrée-sortie les plus « naturels » par rapport à la manière dont on utilise l'ordinateur. Ces fichiers sont déclarés dans <stdio.h>, de la manière suivante :

 
Sélectionnez

FILE *stdin, *stdout, *stderr;
  • stdin est l'unité standard d'entrée. Elle est habituellement affectée au clavier du poste de travail
  • stdout est l'unité standard de sortie. Elle est habituellement affectée à l'écran du poste de travail
  • stderr est l'unité standard d'affichage des erreurs. Elle est aussi affectée à l'écran du poste de travail

Les fonctions de lecture (resp. d'écriture) qui ne spécifient pas un autre flot utilisent stdin (resp. stdout).

Remarque. L'utilisation des unités standard apparait encore plus intéressante lorsque l'on sait que dans UNIX et plusieurs autres systèmes d'exploitation il est permis de réaffecter les unités standard lors d'une exécution du programme, sans qu'il faille recompiler ce dernier. En UNIX et MS-DOS cela fonctionne de la manière suivante : supposons que prog1 soit un programme qui n'utilise que les unités standard stdin et stdout. Activé par la commande prog1, il lira ses données au clavier et affichera ses résultats à l'écran. Mais s'il est lancé par la commande

 
Sélectionnez

prog1 < fichier1 (resp. : prog1 > fichier2)

alors le fichier fichier1 (resp. fichier2) remplacera le clavier (resp. l'écran). De manière similaire, supposons que prog2 soit un autre programme qui fait ses entrées-sorties de la même manière. Alors la commande

 
Sélectionnez

prog1 | prog2

active en parallèle ces deux programmes avec la sortie standard de prog1 connectée à l'entrée standard de prog2 (les sorties de prog1 sont lues et exploitées par prog2 au fur et à mesure qu'elles sont produites par prog1). Bien entendu, tout cela se combine. Par exemple, la commande

 
Sélectionnez

prog1 < fichier1 	| prog2 > fichier2

active prog1 et prog2. Les données de prog1 sont lues dans fichier1, ses résultats sont fournis à titre de données à prog2, dont les résultats sont écrits dans fichier2.

VII-B. Lecture et écriture textuelles

VII-B-1. Lecture et écriture de caractères et de chaines

Les fonctions de lecture-écriture les plus simples effectuent la lecture ou l'écriture d'un caractère isolé ou bien la lecture ou l'écriture d'une ligne. Pour la lecture nous avons :

 
Sélectionnez

int fgetc(FILE *flot)

Renvoie le caractère suivant sur le flot indiqué, ou EOF si la fin du fichier est atteinte ou si une erreur survient. C'est une vraie fonction.

 
Sélectionnez

int getc(FILE *flot)

Fait la même chose que fgetc mais c'est peut être macro.

L'intérêt d'utiliser une macro réside dans l'efficacité supérieure des (petites) macros par rapport aux (petites) fonctions 56. Il ne faut pas oublier cependant qu'il y a deux cas ou les macros ne peuvent pas être utilisées :

  • lorsque l'argument a un effet de bord. Une expression comme
     
    Sélectionnez
    
    *s++ = fgetc(table_de_fichiers[i++])

    est correcte (mais peu utilisée), tandis que

     
    Sélectionnez
    
    *s++ = getc(table_de_fichiers[i++])

    est certainement erronée, car ayant un effet indéfini (on ne peut pas dire combien de fois i sera incrémenté) ;

  • lorsqu'il faut obtenir l'adresse de l'opération en question, pour la passer comme argument d'une autre fonction. Par exemple, si getc est une macro, un appel comme appliquer(getc, fichier) sera certainement trouvé incorrect par le compilateur.
     
    Sélectionnez
    
    int getchar(void)
    getchar() équivaut à getc(stdin) ;
    char *fgets(char *s, int n, FILE *flot)

    Lecture d'une ligne en veillant à ne pas déborder. Plus précisément, cette fonction lit des caractères sur le flot indiqué et les place dans l'espace pointé par s. Elle s'arrête lorsqu'elle a lu n¡1 caractères ou lorsqu'elle a rencontré un caractère 'nn' de fin de ligne (ce caractère est copié dans le tableau). Un caractère 'n0' est ajouté à la fin du tableau. La fonction rend s, ou NULL si une erreur ou la fin du fichier a été rencontrée.

    Lisez aussi la remarque faite ci-après à propos de la fonction gets.

     
    Sélectionnez
    
    char *gets(char *s)

    Lecture d'une ligne sur le flot stdin. Les caractères lus sont rangés dans l'espace pointé par s. Elle renvoie le même résultat que fgets mais, contrairement à cette dernière

  • il n'y a pas de test de débordement 57 ;
  • le caractère de fin de ligne est remplacé par 'n0'.

    Attention. L'argument de gets doit être l'adresse d'un espace disponible (accessible en écriture) et de taille suffisante, dans lequel gets puisse ranger les caractères qui seront lus.

Exemples. Pour illustrer ces fonctions, voici deux exercices d'école. D'une part, l'écriture d'une fonction analogue à gets en utilisant getchar :

 
Sélectionnez

char *myGets(char *s) {
int c, i = 0;
c = getchar();
 
while (c != '\n') {
	if (c == EOF)
		return NULL;
	s[i++] = c;
	c = getchar();
}
s[i] = '\0';
return s;
}

D'autre part, l'écriture d'une fonction analogue à getchar (version fonction) en utilisant gets :

 
Sélectionnez

int myGetchar(void) {
	static char tampon[256] = "";
	static char *pos = tampon;
	if (*pos == 0) {
		if (gets(tampon) == NULL)
			return EOF;
		strcat(tampon, "\n");
		pos = tampon;
	}
	return *pos++;
}

Voyons maintenant les fonctions d'écriture :

 
Sélectionnez

int fputc(int caractere, FILE *flot)

écrit le caractere indiqué sur le flot indiqué. Renvoie le caractère écrit ou EOF si une erreur survient.

C'est une vraie fonction.

 
Sélectionnez

int putc(int caractere, FILE *flot)

Fait la même chose que fputc mais peut être une macro. Voir à ce propos les remarques faites à l'occasion de l'introduction de getc.

 
Sélectionnez

int putchar(int caractere)
putchar(c) équivaut à putc(c, stdout).
int fputs(const char *s, FILE *flot)

écrit la chaine s sur le flot indiqué. La fonction renvoie EOF en cas d'erreur, une valeur ¸ 0 dans les autres cas.

 
Sélectionnez

int puts(const char *s)

Cette fonction écrit la chaine s sur le flot stdout. Elle renvoie le même résultat que fputs. Contrairement à fputs un caractère 'nn' est ajouté à la suite de la chaine.

 
Sélectionnez

int ungetc(int c, FILE *flot)

« Délecture » du caractère c. Cette fonction remet le caractère c dans le flot indiqué, ou il sera trouvé lors de la prochaine lecture. Sur chaque flot, seule la place pour un caractère « délu » est garantie. On ne peut pas « délire » le caractère EOF.

Cette opération incarne donc l'idée de restitution (à l'unité d'entrée, qui en est le fournisseur) d'un caractère lu à tort. C'est une manière de résoudre un problème que pose l'écriture d'analyseurs lexicaux, ou la fin de certaines unités lexicales est indiquée par un caractère qui les suit sans en faire partie. Exemple : la lecture d'un nombre entier non négatif :

 
Sélectionnez

int lirentier(FILE *flot) { /* lecture d'un entier (simpliste) */
int c, x = 0;
c = getc(flot);
while ('0' <= c && c <= '9') {
x = 10 * x + c - '0';
c = getc(flot);
}
/* ici, c est le caractère suivant le nombre */
ungetc(c, flot);
return x;
}

56Il s'agit d'entrées-sorties tamponnées, c'est-à-dire de simples transferts entre mémoires. S'il s'était agi d'opérations physiques, le gain de temps obtenu en utilisant getc à la place de fgetc aurait été négligeable devant le temps requis par l'entrée-sortie elle-même.

57Ainsi, gets lit des caractères jusqu'à la rencontre de 'nn', sans se préoccuper de savoir si l'espace dans lequel ces caractères sont rangés est de taille suffisante pour les recevoir ; autrement dit, tout appel de gets peut entrainer un débordement de mémoire. C'est la raison pour laquelle on dit que tout programme comportant ne serait-ce qu'un appel de gets est bogué.

VII-B-2. Ecriture avec format printf

La fonction d'écriture sur l'unité de sortie standard (en principe l'écran du poste de travail) avec conversion et mise en forme des données est :

 
Sélectionnez

printf( format , expr1 , expr2 , ... exprn )

format est une chaine qui joue un rôle particulier. Elle est formée de caractères qui seront écrits normalement, mélangés à des indications sur les types des expressions expr1, expr2, ... exprn et sur l'aspect sous lequel il faut écrire les valeurs de ces expressions.

Cela fonctionne de la manière suivante : la fonction printf parcourt la chaine format et reconnait certains groupes de caractères comme étant des spécifications de format. C'est le caractère % qui déclenche cette reconnaissance. La fonction printf recopie sur le flot de sortie tout caractère qui ne fait pas partie d'une telle spécification. La ieme spécification détermine la manière dont la valeur de expri sera écrite. Les spécifications de format reconnues par la fonction printf sont expliquées dans la table 4 (page 91).

Notez que seuls les éléments 1 (le caractère %) et 6 (la lettre qui indique le type de la conversion) sont obligatoires dans une spécification de format.

Exemple 1. Supposons qu'on ait donné la déclaration-initialisation

 
Sélectionnez

float x = 123.456;

Voici une liste d'appels de printf, avec l'affichage produit par chacun :

 
Sélectionnez

Appel 					Affichage obtenu
printf(">%f<", x); 			>123.456001<
printf(">%12f<", x); 			> 123.456001<
printf(">%12.2f<", x); 			> 123.46<
printf(">%.2f<", x); 			>123.46<
printf(">%-12.2f<", x); 		>123.46 <
printf(">%+-12.2f<", x); 		>+123.46 <
printf(">% -12.2f<", x); 		> 123.46 <
printf(">%12.2f<", x); 			> 123.46<
printf(">%012.2f<", x); 		>000000123.46<
printf(">%.0f<", x); 			>123<
printf(">%#.0f<", x); 			>123.<

Exemple 2. La liste suivante illustre l'utilisation de la largeur et de la précision pour les chaines. Supposons avoir donné les déclarations :

 
Sélectionnez

char *s = "ABCDEFG";
int l = -10, n = 5;

Voici une liste d'appels de printf, avec l'affichage produit par chacun :

 
Sélectionnez

Appel 							Affichage obtenu
printf("printf(">%5s<", s); 				>ABCDEFG<
printf("printf(">%10s<", s); 				> ABCDEFG<
printf("printf(">%-10s<", s); 				>ABCDEFG <
printf("printf(">%10.5s<", s); 				> ABCDE<
printf("printf(">%-10.5s<", s); 			>ABCDE <
printf("printf(">%.5s<", s); 				>ABCDE<
printf("printf(">%*.*s<", l, n, s); 			>ABCDE <

La largeur du champ (cf. tableau 4, nº3) peut être indiquée par le caractère * à la place d'un nombre. Elle est alors définie par la valeur de l'argument dont c'est le tour, qui doit être de type entier. Il en est de même pour la spécification de la précision (cf. tableau 4, nº4). Par exemple, i et j étant des variables entières et x une variable flottante, l'expression

 
Sélectionnez

printf("%*.*f", i, j, x);

écrit la valeur de x sur un nombre de caractères égal à la valeur de i, avec un nombre de chiffres après la virgule égal à la valeur de j.

Tab. 4 - Spécifications de format pour printf, fprintf et sprintf
Tab. 4 - Spécifications de format pour printf, fprintf et sprintf

VII-B-3. Lecture avec format scanf

La fonction de lecture avec format à l'unité d'entrée standard (en principe le clavier du poste de travail) est

 
Sélectionnez

scanf( format , adresse1 , adresse2 , ... adressen )

L'argument format est une chaine qui indique la manière de convertir les caractères qui seront lus ; adresse1, adresse2, ... adressen indiquent les variables devant recevoir les données lues. Ce sont des « sorties » de la fonction scanf, par conséquent il est essentiel que ces arguments soient des adresses de variables à lire.

La fonction scanf constituant un vrai analyseur lexical, les données lues ont de nombreuses occasions d'être incorrectes et on peut s'attendre à ce que scanf soit d'un maniement difficile. L'expérience ne déçoit pas cette attente ! Voici un bon conseil : si un programme ne fonctionne pas comme il le devrait, commencez par vérifier les valeurs lues par la fonction scanf (par exemple avec des appels de printf suivant immédiatement les appels de scanf).

Les spécifications de format reconnues par la fonction scanf sont expliquées dans la table 5 (page 93). Fonctionnement : scanf parcourt le format. Elle rend la main lorsque la fin du format est atteinte ou sur une erreur. Jusque-là :

  • Tout caractère ordinaire du format, c'est-à-dire qui n'est ni un caractère d'espacement (blanc, tabulation) ni un caractère faisant partie d'une spécification de format (commençant par %), doit s'identifier au caractère courant du flot d'entrée. Si cette identification a lieu, le caractère courant est lu, sans être rangé dans aucune variable, et le parcours du format se poursuit. Si l'identification n'a pas lieu, l'activation de scanf se termine.
  • Les spécifications de format commencent par %. Elles indiquent la manière d'analyser les caractères lus sur le flot d'entrée et de ranger les valeurs ainsi obtenues. Voir le tableau 5.
  • Dans le format, les caractères ordinaires et les spécifications peuvent être séparés entre eux par des caractères d'espacement. Le nombre de ces espacements est sans importance, mais leur présence indique que les données correspondantes peuvent être séparées dans le flot d'entrée par un nombre quelconque de caractères d'espacement ou de fin de ligne.
  • S'il n'y a pas d'espacement, dans le format, entre les caractères ordinaires ou les spécifications, alors les données correspondantes dans le flot d'entrée doivent être adjacentes.
  • Cependant, les espacements au début des nombres sont toujours sautés.

On peut mélanger des appels de scanf et d'autres fonctions de lecture. Chaque appel d'une telle fonction commence par lire le premier caractère que l'appel précédent n'a pas « consommé ».

Tab. 5 - Spécifications de format pour scanf, fscanf et sscanf
Tab. 5 - Spécifications de format pour scanf, fscanf et sscanf

Exemple 1. Lecture d'un entier :

 
Sélectionnez

scanf("%d", &x);

Exemple 2. Lecture de deux entiers séparés par une virgule et encadrés par une paire de parenthèses :

 
Sélectionnez

scanf("(%d,%d)", &amp;x, &amp;y);

Données correctes :

(22,33) ou ( 22, 33)

(des blancs, mais uniquement devant les nombres).

Données incorrectes :

( 22 , 33 )

(trop de blancs).

Exemple 3. Comme ci-dessus mais avec une syntaxe plus souple (les blancs sont tolérés partout) :

 
Sélectionnez

scanf(" (%d ,%d )", &x, &y);

Toutes les données de l'exemple précédent sont correctes. Exemple de donnée incorrecte :

( 22 ; 33 )

Exemple 4. Lecture d'un caractère 58 :

 
Sélectionnez

char calu;
...
scanf("%c", &calu);

Remarque. Nous avons indiqué par ailleurs qu'en C on range souvent les caractères dans des variables de type int afin de permettre la mémorisation d'une valeur « intruse », EOF. Cependant, notez bien qu'ici nous sommes obligés de déclarer la variable calu de type char ; le programme

 
Sélectionnez

int calu;
...
scanf("%c", &calu);

aurait été incorrect (bien qu'il puisse fonctionner correctement sur certains systèmes), car on y fournit à scanf l'adresse d'un entier (fait de plusieurs octets) pour y ranger un caractère, alors que scanf, d'après l'analyse du format, « croit » recevoir l'adresse d'un char (un octet). Or on ne doit pas faire d'hypothèse sur la manière dont sont organisés les octets qui composent un entier, le langage C ne précisant rien à ce sujet. Une autre solution correcte aurait été la suivante :

 
Sélectionnez

int calu;
char tmp;
...
scanf("%c", &tmp);
calu = tmp;

Exemple 5. Lecture d'un nombre entier et du caractère le suivant immédiatement, quel qu'il soit :

 
Sélectionnez

int nblu;
char calu;
...
scanf("%d%c", &nblu, &calu);

Exemple 6. Lecture d'un nombre entier et du premier caractère non blanc le suivant :

 
Sélectionnez

int nblu;
char calu;
...
scanf("%d %c", &nblu, &calu);

58Rappelons que fgetc, getc et getchar font ce travail bien plus simplement.

VII-B-4. A propos de la fonction scanf et des lectures interactives

A propos de « sauter les blancs ». Nous avons dit que les spécifications qui constituent le format peuvent être séparées entre elles par un ou plusieurs blancs, ce qui indique que les données à lire correspondantes peuvent alors être séparées par un nombre quelconque de blancs.

Pour la fonction scanf, les blancs dans le format se traduisent donc par l'ordre « à présent, sautez tous les caractères blancs qui se présentent ». Or la seule manière pour l'ordinateur de s'assurer qu'il a bien sauté tous les caractères blancs consiste à lire (sans le consommer) le premier caractère non blanc qui les suit. Si la lecture se fait dans un fichier, cela ne pose pas de problème, les caractères à lire existant tous dès le départ. Mais cela peut créer des situations confuses dans le cas d'une lecture interactive mettant en œuvre le clavier et un opérateur vivant. Exemple :

 
Sélectionnez

int x, y = 33;
...
printf("donne x : ");
scanf(" %d ", &x); 		/* aïe... */
printf("donne y : ");
scanf("%d", &y);
printf("x = %d, y = %d\n", x, y);

Parce qu'on a écrit, peut-être sans faire attention, des blancs après le format %d, le premier appel de scanf ci-dessus signifie « lisez un entier et tous les blancs qui le suivent ». L'opérateur constatera avec étonnement que pour que son premier nombre sont pris en compte il est obligé de composer le deuxième !

Exemple de dialogue (le signe ↵ représentant la frappe de la touche « Entrée ») :

 
Sélectionnez

donne x : 11↵
↵
↵ la machine ne réagit pas... ! ?
22↵
donne y : x = 11, y = 22
			

Notez que faire suivre le premier nombre d'un caractère non blanc conventionnel n'est pas une bonne solution car, ce caractère n'étant pas consommé, il fait échouer la lecture du second nombre :

 
Sélectionnez

donne x : 11↵
donne y : x = 11, y = 33
			

Leçon à retenir : dans le format de scanf, les blancs ne sont pas de la décoration, ils jouent un rôle bien précis.

A propos de la lecture d'un caractère. Une autre situation génératrice de confusion est celle ou des lectures de nombres sont suivies par des lectures de caractères ou de chaines. Par exemple, le programme naïf suivant lit des nombres les uns à la suite des autres et affiche le carré de chacun :

 
Sélectionnez

int x, c;
...
do {
	printf("donne un nombre : ");
	scanf("%d", &x); 	/* lecture d'un nombre */
	printf("%d ^ 2 = %d\n", x, x * x);
	printf("un autre calcul (O/N) ? ");
	c = getchar(); 		/* lecture d'un caractère */
}
while (c == 'O');
printf("au revoir...");\end{verbatim}

Voici un dialogue (la réplique de l'opérateur est soulignée, le signe ↵ représente la frappe de la touche « Entrée ») :

 
Sélectionnez

donne un nombre : 14↵
14 ^ 2 = 196
un autre calcul (O/N) ? au revoir...
			

La machine n'a pas attendu la réponse (O ou N) de l'opérateur... ! Que s'est-il passé ? L'opérateur a composé un nombre suivi immédiatement d'un caractère fin-de-ligne (traduction de la frappe de la touche « Entrée »).

Cette fin de ligne est inévitable mais n'a pas été consommée par scanf et est restée disponible pour des lectures ultérieures. L'appel de getchar (qui, dans l'esprit du programmeur, aurait dû provoquer une nouvelle lecture physique) trouve cette fin-de-ligne et l'affecte à c comme réponse, ce qui fait quitter la boucle. Ainsi, pour que ce programme se comporte comme son programmeur attend il faudrait qu'à la suite de l'invite « donne un nombre : », l'utilisateur saisisse un nombre immédiatement suivi d'un caractère O ou N qui est la réponse à une question qui ne lui a pas encore été posée !

Cela n'est pas raisonnable, bien sûr. La bonne manière de faire consiste à vider le tampon d'entrée avant de procéder à une lecture de caractère. Dans beaucoup d'environnements on peut faire cela avec la fonction

 
Sélectionnez

fflush :
int x, c;
char s[BUFSIZ];
...
do {
	printf("donne un nombre : ");
	scanf("%d", &x);
	printf("%d ^ 2 = %d\n", x, x * x);
	printf("un autre calcul (O/N) ? ");
	/* ici, le tampon de stdin contient (au moins) un 'nn' */
	fflush(stdin);
	/* maintenant le tampon est vide */
	c = getchar();
}
while (c == 'O');
printf("au revoir...");
...

Si la fonction fflush est inopérante sur le flot stdin (un tel comportement est permis par la norme du langage C) alors on peut résoudre le même problème en faisant des appels répétés de la fonction getchar, de manière à consommer tous les caractères restant dans le tampon jusqu'à la fin-de-ligne, ce caractère devant être consommé lui aussi. L'exemple ci-dessus devient

 
Sélectionnez

...
scanf("%d", &x);
printf("%d ^ 2 = %d\n", x, x * x);
printf("un autre calcul (O/N) ? ");
/* ici, le tampon de stdin contient (au moins) un 'nn' */
while (getchar() != '\n')
;
/* maintenant le tampon est vide */
c = getchar();
...

VII-B-5. Les variantes de printf et scanf

Voici la famille au complet, comme elle est déclarée (en syntaxe ANSI) dans le fichier <stdio.h> :

printf

 
Sélectionnez

int printf(const char *format, ... )

Nous avons décrit cette fonction. Ajoutons seulement qu'elle renvoie le nombre de caractères effectivement écrits (on utilise rarement cette information) ou un nombre négatif en cas d'erreur.

fprintf

 
Sélectionnez

int fprintf(FILE *flot, const char *format, ... )

Comme printf mais sur le flot indiqué (à la place de stdout). Ainsi

 
Sélectionnez

printf( format , exp1 , ... , expk )

équivaut à

 
Sélectionnez

fprintf(stdout, format , exp1 , ... , expk )
int sprintf(char *destination, const char *format, ... )

Cette fonction effectue les mêmes mises en forme que ses deux sœurs, avec la différence que les caractères produits ne sont pas ajoutés à un flot, mais sont concaténés ensemble dans la chaine destination. Elle permet donc de dissocier les deux fonctionnalités de printf, transcodage et écriture des données, afin d'utiliser la première sans la deuxième.

sscanf

 
Sélectionnez

int scanf(const char *format, ... )

Nous avons décrit cette fonction. Ajoutons qu'elle renvoie EOF si une fin de fichier ou une erreur a empêché toute lecture ; autrement elle rend le nombre de variables lues avec succès. Cette indication fournit une manière bien pratique pour détecter, dans les programmes simples, la fin d'une série de données. Exemple :

 
Sélectionnez

for(;;) {
	printf("donne un nombre : ");
	if (scanf("%d", &x) < 1)
		break;
	/* exploitation du nombre lu */
}
printf("au revoir...");

La boucle précédente sera arrêtée par n'importe quel caractère non blanc ne pouvant pas faire partie d'un nombre.

fscanf

 
Sélectionnez

int fscanf(FILE *flot, const char *format, ... )

Comme scanf mais sur le flot indiqué (à la place de stdin). Ainsi

 
Sélectionnez

scanf( format , exp1 , ... , expk )

équivaut à

 
Sélectionnez

fscanf(stdin, format , exp1 , ... , expk )
int sscanf(const char *source, const char *format, ... )

Cette fonction effectue la même analyse lexicale que ses sœurs, avec la particularité que le texte analysé n'est pas pris dans un flot, mais dans la chaine source.

VII-C. Opérations en mode binaire

VII-C-1. Lecture-écriture

fread

 
Sélectionnez

size t fread(void *destination, size t taille, size t nombre, FILE *flot)

Cette fonction essaye de lire sur le flot indiqué nombre objets, chacun ayant la taille indiquée, et les copie les uns à la suite des autres dans l'espace pointé par destination. Elle renvoie le nombre d'objets effectivement lus, qui peut être inférieur au nombre demandé, à cause de la rencontre de la fin du fichier 59, d'une erreur, etc.

fwrite

 
Sélectionnez

size t fwrite(const void *source, size t taille, size t nombre, FILE *flot)

Cette fonction écrit les nombre objets, chacun ayant la taille indiquée, qui se trouvent les uns à la suite des autres à l'adresse indiquée par source. Elle renvoie le nombre d'objets écrits, qui peut être inférieur au nombre demandé (en cas d'erreur).

59Lorsqu'un fichier est lu par des appels de la fonction fread, comparer avec zéro le nombre d'objets effectivement lus est la manière la plus pratique de détecter la fin du fichier.

VII-C-2. Positionnement dans les fichiers

fseek

 
Sélectionnez

int fseek(FILE *flot, long deplacement, int origine)

Cette fonction positionne le pointeur du fichier associé au flot indiqué. La première lecture ou écriture ultérieure se fera à partir de la nouvelle position. Celle-ci est obtenue en ajoutant la valeur de déplacement à une valeur de base qui peut être la position courante, la fin ou le début du fichier. C'est l'argument origine qui indique de quelle base il s'agit ; la valeur de cet argument doit être une des constantes (définies dans <stdio.h>) :

  • SEEK SET : base = le début du fichier
  • SEEK CUR : base = la position courante
  • SEEK END : base = la fin du fichier

La fonction renvoie zéro en cas de succès, une valeur non nulle en cas d'erreur. Sur un fichier de texte, il vaut mieux utiliser getpos et setpos.

Un appel de cette fonction sur un fichier en écriture produit la vidange du tampon (fseek implique fflush). Lire ci-après les explications sur l'accès relatif aux fichiers.

frewind

 
Sélectionnez

void rewind(FILE *flot)

Positionnement au début du fichier du pointeur de fichier associé au flot indiqué. Un appel de cette fonction équivaut à fseek(flot, 0L, SEEK SET) suivi de la mise à zéro de tous les indicateurs d'erreur.

fgetpos

 
Sélectionnez

void fgetpos(FILE *flot, fpos t *ptr)

Place dans ptr la position courante dans le flot indiqué en vue de son utilisation par setpos. Le type fpos t (défini dans <stdio.h>) a une taille suffisante pour contenir une telle information.

fsetpos

 
Sélectionnez

void fsetpos(FILE *flot, const fpos t *ptr)

La valeur de ptr provenant d'un appel de getpos, cette fonction positionne le flot indiqué à l'emplacement correspondant.

L'accès relatif aux éléments des fichiers. La principale application des fonctions de positionnement est la réalisation de ce qu'on appelle l'accès relatif 60 ou parfois accès direct aux composantes d'un fichier : l'accès à une composante (on dit un enregistrement) que ce soit pour le lire ou pour l'écrire, à partir de la donnée de son rang dans le fichier sans être obligé d'accéder au préalable aux enregistrements qui le précèdent.

Pour donner une signification utilisable à la notion de « neme article », il faut que le fichier puisse être vu comme une suite d'articles de même taille (mais la bibliothèque C ignorera ce fait ; le fichier restera une suite d'octets). Cela s'obtient généralement en déclarant une structure qui représente un article, selon le modèle :

 
Sélectionnez

typedef struct {
...

champs dont se composent tous les articles

 
Sélectionnez

...
} ARTICLE;...

Un fichier destiné à être utilisé en accès relatif sera déclaré normalement, et ouvert de fa»con à autoriser les lectures et les écritures :

 
Sélectionnez

FILE *fichier;
...
fichier = fopen(nom , "rb+");

Si les articles sont numérotés à partir de zéro, l'opération « lecture de l'enregistrement de rang n » se programme alors selon le schéma suivant :

 
Sélectionnez

ARTICLE article;
...
fseek(fichier, n * sizeof(ARTICLE), SEEK_SET);
fread( & article, sizeof(ARTICLE), 1, fichier);
...
/* exploitation des valeurs des champs de article */
...

L'opération « écriture de la composante de rang n » obéit au schéma

 
Sélectionnez

ARTICLE article;
...
/* affectation des champs de article */
...
fseek(fichier, n * sizeof(ARTICLE), SEEK_SET);
fwrite( & article, sizeof(ARTICLE), 1, fichier);
...

Attention. La possibilité d'effectuer des lectures et des écritures sur le même fichier pose des problèmes dans la gestion des tampons associés au fichier que les fonctions de la bibliothèque standard ne résolvent pas. Ainsi chaque fois qu'une série de lectures sur un fichier va être suivie d'une série d'écritures sur le même fichier, ou réciproquement, il appartient au programmeur de prévoir l'écriture effective du tampon, soit par un appel explicite de la fonction fflush, soit par un appel d'une fonction de positionnement comme fseek. Notez que cette contrainte est satisfaite si on pratique des accès relatifs selon les schémas indiqués ci-dessus, puisqu'il y a toujours un appel de fseek entre deux accès au fichier, quel que soit leur mode.

60L'accès relatif s'oppose à l'accès séquentiel, dans lequel une composante ne peut être lue ou écrite autrement qu'à la suite de la lecture ou de l'écriture de la composante qui la précède.

VII-D. Exemples

Les programmes suivants sont assez naïfs ; ils se proposent d'illustrer les fonctions de traitement de fichiers, aussi bien pour les fichiers de texte que pour les fichiers binaires.

VII-D-1. Fichiers "en vrac"

Le programme suivant fait une copie identique d'un fichier (ce programme n'est pas bien utile, généralement une fonction du système d'exploitation fait ce travail) :

 
Sélectionnez

#include <stdio.h>
#define PAS_D_ERREUR 		0 /* codes conventionnels */
#define ERREUR_OUVERTURE 	1 /* à l'usage du système */
#define ERREUR_CREATION 	2 /* d'exploitation */
FILE *srce, *dest;
 
main() {
	char tampon[512];
	int nombre;
 
	printf("source : ");
	gets(tampon);
	srce = fopen(tampon, "rb");
	if (srce == NULL)
		return ERREUR_OUVERTURE;
 
	printf("destination : ");
	gets(tampon);
	dest = fopen(tampon, "wb");
	if (dest == NULL)
	return ERREUR_CREATION;
 
	while ((nombre = fread(tampon, 1, 512, srce)) > 0)
		fwrite(tampon, 1, nombre, dest);
 
	fclose(dest);
	fclose(srce);
	return PAS_D_ERREUR;
}

L'essentiel de ce programme est sa boucle while. Elle se compose d'un appel de fread demandant la lecture de 512 octets, suivi d'un appel de fwrite pour écrire les nombre octets effectivement lus. Supposons que le fichier à copier comporte N octets ; ces deux opérations seront exécutées N 512 fois avec nombre égal à 512 puis

(sauf si N est multiple de 512) une dernière fois avec nombre égal au reste de la division de N par 512.

VII-D-2. Fichiers binaires et fichiers de texte

L'objet du programme suivant est la constitution d'un fichier d'articles à partir d'un fichier de texte qui se présente comme une répétition du groupe suivant :

  • un nom et un prénom sur la même ligne ;
  • une adresse sur la ligne suivante ;
  • un entier seul sur une ligne, exprimant un certain « nombre de passages » (à un péage autoroutier).

Exemple

 
Sélectionnez

TOMBAL Pierre
Rue de la Gaiete de Vivre
	10
 
MENSOIF Gerard
Allee 'Les verts'
	20
		etc.

Le programme est indépendant de la nature du fichier de texte, qui peut être aussi bien le clavier d'un terminal qu'un fichier existant par ailleurs.

 
Sélectionnez

/* constitution d'un fichier d'articles */
/* à partir d'un fichier de texte */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define PAS_D_ERREUR 		0
#define ERREUR_OUVERTURE 	1
#define ERREUR_CREATION 	2
#define ERREUR_ECRITURE 	3
#define ERREUR_FERMETURE 	4
 
struct 					/* un article du fichier */
	{
	char nom[32], prenom[32];
	char adresse[80];
	int nombre_de_passages;
	} art;
 
FILE *srce; 		/* fichier de texte */
FILE *dest; 		/* fichier binaire */
 
main() {
	char nom[256];
 
	printf("source : ");
	gets(nom);
	if ((srce = fopen(nom, "r")) == NULL)
		exit(ERREUR_OUVERTURE);
	printf("destination : ");
	gets(nom);
	if ((dest = fopen(nom, "wb")) == NULL)
		exit(ERREUR_CREATION);
 
for (;;) {
	if (fscanf(srce, " %s %s ", art.nom, art.prenom) != 2)
		break;
	fgets(art.adresse, 80, srce);
 
							/* enlevons le '\n' : */
	art.adresse[strlen(art.adresse) - 1] = '\0';
	fscanf(srce, " %d ", &art.nombre_de_passages);
	if (fwrite(&art, sizeof art, 1, dest) != 1)
		exit(ERREUR_ECRITURE);
}
if (fclose(dest) != 0)
	exit(ERREUR_ECRITURE);
exit(PAS_D_ERREUR);
}

Notez les diverses fonctions qui ont été utilisées pour lire du texte. Le nombre de passages est lu par scanf, car cette fonction est la seule qui convertit les nombres. Le nom et le prénom sont lus par scanf aussi, car cela nous arrange bien que les blancs soient pris pour des séparateurs et enlevés. Mais l'adresse est lue par gets, car elle peut contenir des blancs qui ne doivent pas être supprimés.

VII-D-3. Fichiers en accès relatif

Dans le programme précédent nous avons vu un fichier binaire organisé en articles et traité séquentiellement (chaque article étant écrit à la suite du précédent). Voyons un exemple ou ce même fichier est traité en accès relatif.

Imaginons que le fichier créé à l'exemple précédent soit le fichier des abonnés à un certain péage, et que nous disposions par ailleurs d'un fichier binaire contenant les numéros des abonnés qui ont emprunté ce péage durant une période donnée. On nous demande d'écrire un programme pour incrémenter la valeur du champ nbr passages de chaque abonné concerné.

 
Sélectionnez

#include <stdio.h>
 
#define PAS_D_ERREUR 				0
#define ERREUR_OUVERTURE_ABONNES 	1
#define ERREUR_OUVERTURE_PASSAGES 	2
 
struct
	{
	char nom[32], prenom[32];
	char adresse[80];
	int nombre_de_passages;
	} art;
 
	FILE *passages, *abonnes;
 
	main() {
		char nom[256];
		long n;
 
		printf("fichier des passages : ");
		if ((passages = fopen(gets(nom), "rb")) == NULL)
			exit(ERREUR_OUVERTURE_PASSAGES);
		printf("fichier des abonnes : ");
		if ((abonnes = fopen(gets(nom), "rb+")) == NULL)
			exit(ERREUR_OUVERTURE_ABONNES);
 
		for (;;) {
			if (fread(&n, sizeof n, 1, passages) != 1)
				break;
			fseek(abonnes, n * sizeof art, SEEK_SET);
			fread(&art, sizeof art, 1, abonnes);
			art.nombre_de_passages++;
			fseek(abonnes, n * sizeof art, SEEK_SET);
			fwrite(&art, sizeof art, 1, abonnes);
			}
 
		fclose(abonnes);
		exit(PAS_D_ERREUR);
}

VII-E. Les fichiers de bas niveau d'UNIX

Les fichiers de bas niveau du système UNIX ne sont pas fondamentalement distincts des flots que l'on vient de décrire. En fait, les flots et les fichiers de bas niveau sont deux manières de voir depuis un programme les mêmes entités de base (les fichiers). Les fichiers de bas niveau sont la manière habituelle de réaliser les fichiers binaires lorsqu'on ne possède pas un C ANSI.

Dans le système UNIX chaque processus dispose, pour gérer ses fichiers, d'une table de descripteurs de fichiers. Lorsque les fichiers sont gérés au plus bas niveau, ils sont représentés tout simplement par un indice dans cette table. Par exemple, les trois unités standard stdin, stdout et stderr sont respectivement représentées en tant que fichiers par les descripteurs d'indices 0, 1 et 2.

Les fonctions disponibles sont :

open

 
Sélectionnez

int open(char *nom, int mode, int permissions)

Ouvre un fichier existant ayant le nom externe indiqué. L'argument mode doit avoir pour valeur une des constantes symboliques (définies dans <ioctl.h> ou dans <sys/file.h>) :

  • O RDONLY : ouverture en lecture seulement
  • O WRONLY : ouverture en écriture seulement
  • O RDWR : ouverture en lecture / écriture

Cette fonction renvoie le numéro du descripteur ouvert pour le fichier, ou un nombre négatif en cas d'erreur. Pour l'argument permissions voir ci-dessous.

creat

 
Sélectionnez

int creat(char *nom, int permissions)

Crée un fichier nouveau, ayant le nom indiqué, et l'ouvre en écriture. Comme pour la fonction open, l'argument permissions indique que le droit de lecture, le droit d'écriture ou le droit d'exécution est accordé ou non au propriétaire du fichier, aux membres de son groupe de travail ou à tous les utilisateurs du système. Cela fait trois groupes de trois bits, soit un nombre qui s'écrit particulièrement bien en octal.

Par exemple :

 
Sélectionnez

fic = creat("tmp/monfic", 0751);

crée un fichier nouveau et lui accorde les permissions :

  lecture écriture exécution en octal
propriétaire 1 1 1 7
groupe 1 0 1 5
autres 0 0 1 1

Lit nombre octets depuis le fichier indiqué et les range à partir de l'adresse indiquée. Renvoie le nombre d'octets effectivement lus, qui peut être inférieur à nombre (fin de fichier, erreur de lecture, etc.).

write

 
Sélectionnez

int write(int fichier, void *adresse, int nombre)

Ecrit les nombre octets qui se trouvent à l'adresse indiquée dans le fichier indiqué. Rend le nombre d'octets effectivement écrits, qui peut être inférieur à nombre (erreur d'écriture, etc.).

close

 
Sélectionnez

int close(int descfic)

Ferme le fichier indiqué.

lseek

 
Sélectionnez

long lseek(int fichier, long rang, int origine)

Positionne le fichier indiqué sur l'octet ayant le rang indiqué. Ce rang exprime une position...

 
Sélectionnez

...relative au début du fichier (si origine = 0)
...relative à la position courante (si origine = 1)
...relative à la fin du fichier (si origine = 2)

Exemple. Voici la version UNIX (et, de plus, non ANSI) du programme, donné plus haut, qui effectue une copie identique d'un fichier quelconque. Au sujet de l'utilisation qui est faite des arguments de main voir la section 8.3.1.

 
Sélectionnez

main(int argc, char *argv[]) {
	int srce, dest, n;
	char tampon[512];
	if (argc < 3
			|| (srce = open(argv[1], 0, 0777)) < 0
			|| (dest = creat(argv[2], 0777)) < 0)
		return 1;
	while ((n = read(srce, tampon, 512)) > 0)
		write(dest, tampon, n);
	close(dest);
	close(srce);
	return 0;
}

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 1988-2005 Henri Garreta. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.