Page suivante Page précédente Table des matières
10. La bibliothèque standard sous Linux : première partie
Par James M. Rogers.
Le C est un très petit langage, c'est bien. Les programmeurs C utilisent presque tout le langage C quand ils écrivent un programme de taille raisonnable. La bibliothèque C standard étend les fonctions de C dans la mesure où l'on peut la trouver sur de nombreux systèmes. La bibliothèque fournit des outils comme
scanf()
etprintf()
qui rendent la lecture et les sorties formatées bien plus aisées qu'avec des blocs de caractères à l'aide des fonctionsread()
etwrite()
. De plus, lorsque vous passez d'un environnement de programmation C à un autre, les fonctionnalités deprintf()
seront les mêmes. Vous n'avez pas à réapprendre comment effectuer des sorties formatées chaque fois que vous changez de machine.Dans cette série d'articles, je traiterai des outils qui sont à la disposition du programmeur dans la bibliothèque standard C. À la fin de l'article, vous trouverez une bibliographie des livres et articles que j'ai utilisés comme base. Je me réfère à ces publications de manière quotidienne quand je programme. Si vous voulez programmer en C, je vous recommande d'acheter et de lire ces livres et articles.
Très souvent, les fonctions standards sont méprisées et réinventées par les programmeurs (dont je fais partie !) pour faire des choses comme savoir si un caractère est une lettre :
(c=>'a'|| c<='z' && c=>'A' || c<='Z')
au lieu d'utiliser :
isalpha(c);
La seconde forme est plus facile à lire et à comprendre. Il y a une autre raison d'utiliser celle-ci. Le premier exemple ne marche que pour les jeux de caractères ASCII, la seconde marchera sur toutes les machines. Si vous voulez écrire du code portable (du code qui peut être compilé et exécuté sur n'importe quelle machine avec très peu de changements), vous devez utiliser la bibliothèque C standard.
Plusieurs fois dans le passé, j'ai écrit du code qui m'as pris du temps à écrire, à déboguer et à interfacer avec le reste de mon programme pour seulement découvrir qu'il existait une fonction qui faisait ce que je voulais. Il y a quelques mois, j'ai écrit mon propre Donjon Multi-utilisateurs (Multi User Dimension) basé sur un article sur le client/serveur dans le Linux Journal et je devais traiter ce que l'utilisateur avait entré, un mot à la fois. J'ai donc écrit ma propre fonction de découpage de chaîne. En fait, j'aurais pu utiliser la fonction
strtok()
qui fait exactement la même chose. Et les autres sauront ce que la fonctionstrtok()
fait sans avoir à déchiffrer mon code.Rendez vous la vie plus facile, utilisez la bibliothèque C standard. Cela sera aussi une grande aide pour tous ceux qui essayeront de mettre à jour et de maintenir votre code dans le futur.
Le compilateur GNU, gcc, est fourni avec avec la bibliothèque C standard GNU. Ce compilateur est l'un des meilleurs au monde et la bibliothèque C standard GNU se conforme exactement à la norme. Aux endroits où la norme est imprécise, vous pouvez vous attendre à un comportement correct de la part de la bibliothèque et du compilateur. Je parlerai de la bibliothèque C standard GNU dans ces articles.
La bibliothèque
<stdio.h>
s'occupe des fonctions d'entrées/sorties standards pour les programmeurs C. C'est aussi, et de loin, la plus grosse bibliothèque. du fait de sa taille particulièrement importante. Je grouperai ces commandes dans les sections suivantes : opérations sur les fichiers, entrées et sorties.Puisque nous parlons des fichiers, nous devons nous mettre d'accord sur les mots que nous allons utiliser. Sous Linux, un fichier ou un périphérique sont considérés comme des flux de données. Ce flux est associé au fichier ou au périphérique et est accédé par l'ouverture du fichier ou du périphérique par votre programme. Une fois le flux ouvert, vous pouvez y lire ou y écrire.
Trois flux sont ouverts automatiquement quand vous exécutez un programme. L'entrée standard (stdin), la sortie standard (stdout) et l'erreur standard (stderr). Ils peuvent tous être redirigés par votre shell quand vous exécutez le programme mais, normalement, stdin équivaut à votre clavier et stdout et stderr à votre moniteur.
Après vous être occupés des flux, vous devez dire au système d'exploitation de vider les tampons et de finir de sauver les données dans les périphériques. Vous faites cela en fermant le flux. Si vous ne fermez pas votre flux, il est possible que vous perdiez des données. Stdin, stdout et stderr sont tous fermés automatiquement, de la même manière qu'ils avaient été ouverts automatiquement.
Une des choses les plus importantes à se rappeler lorsque vous avez affaire à des périphériques et à des fichiers est que vous intervenez au niveau du monde réel. Ne faites pas comme si votre fonction allait fonctionner. Même une fonction telle que
printf
peut échouer. Les disques se remplissent, sont parfois défectueux, les utilisateurs entrent de mauvaises données, les processeurs sont surchargés, d'autres programmes verrouillent vos fichiers. La Loi de Murphy s'applique parfaitement aux systèmes informatiques. Toute fonction qui intervient sur le monde réel renvoie un code d'erreur en cas d'échec. Vérifiez toujours les valeurs de retour et prenez les dispositions appropriées quand vous êtes confrontés à une condition d'erreur. Les exceptions ne sont pas des erreurs à moins qu'elles soient mal traitées (William Kahan, au sujet du traitement des exceptions).Le premier exemple vise à montrer comment ouvrir un fichier en lecture. Sa seule action est de renvoyer vers la sortie standard le contenu d'un fichier appelé
test
. Toutes les exceptions sont envoyées vers la sortie d'erreur standard, le programme étant alors stoppé avec un code d'erreur. Il produit une erreur si le fichiertest
n'est pas présent.
#include <stdio.h> /* c'est une directive de compilation qui dit au compilateur d'utiliser les fonctions de la bibliothèque standard des entrées/sorties */ main (){ /* déclaration des variables */ FILE *stream; /* nécessite un pointeur vers FILE pour le flux */ int buffer_character; /* nécessite un int pour conserver un caractère */ /* ouvre le fichier test du répertoire courant en lecture */ stream = fopen("test", "r"); /* Si le fichier n'est pas ouvert correctement, stream sera égal à NULL. Il est maintenant courant de représenter NULL en transtypant la valeur 0 vers le type correct par vous-même plutôt que de laisser le compilateur deviner lui-même du type de NULL à utiliser */ if (stream == (FILE *)0) { fprintf(stderr, "Erreur d'ouverture de fichier (envoyé sur l'erreur standard)\n"); exit (1); } /* end if */ /* lecture et écriture du fichier un caractère à la fois jusqu'à la fin du fichier, de lecture ou d'écriture. Si le EOF est atteint sur le descripteur de fichier, on sort de la boucle while. Si l'EOF intervient sur l'écriture vers la sortie standard on sort du programme avec une condition d'erreur */ while ((buffer_character=getc(stream))!=EOF) { /* écriture le caractère vers la sortie standard et vérification des erreurs */ if((putc(buffer_character, stdout)) == EOF) { fprintf(stderr,"Erreur d'écriture vers la sortie standard. (imprimé sur l'erreur standard)\n"); fclose(stream); exit(1); } /* end if */ } /* end while */ /* fermeture du fichier après en avoir fini; en cas d'échec de fermeture, la signaler et terminer */ if ((fclose(stream)) == EOF) { fprintf(stderr,"Erreur de fermeture du flux. (imprimé sur l'erreur standard)\n"); exit(1); } /* end if */ /* rapport de succès vers l'environnement */ return 0; } /* end main*/
Le simple programme ci-dessus est un exemple d'ouverture, de lecture puis d'écriture sur un fichier à l'aide de stdout et stderr. J'ai copié-collé le code dans une session vi, puis je l'ai sauvé, compilé et exécuté.
Ce qui suit est un résumé rapide des opérations sur les fichiers dans la bibliothèque <stdio.h>. Ce sont des opérations qui marchent directement sur les flux.
10.1 Ouverture de flux
Avant de pouvoir utiliser un flux, vous devez l'associer à un périphérique ou à un fichier. On appelle ça "ouvrir le flux". Votre programme demande à votre système d'exploitation de lire ou d'écrire vers un périphérique. Si vous avez les bons droits, si le fichier existe ou si vous pouvez créer le fichier et que personne n'a verrouillé le fichier, alors le système d'exploitation vous permet d'ouvrir le fichier et vous renvoie un objet, le flux. En utilisant cet objet, vous pouvez lire et écrire vers le flux et, quand vous avez fini, vous pouvez fermer celui-ci.
Je vais maintenant vous décrire le format des descriptions que vous verrez ici et dans les pages de man. La première entrée est le type qui est retourné par l'appel de fonction. La seconde est le nom de la fonction elle-même et la troisième est ma liste des types de variables que la fonction prend en arguments.
En regardant la première ligne ci-dessous, vous pouvez voir que la fonction
fopen
prend deux pointeurs sur des chaînes, le premier étant le chemin vers un fichier et le second étant le mode d'ouverture du programme. La fonction retourne un pointeur vers un typeFILE
qui est un objet composé défini dans<stdio.h>
. De manière à recueillir le type de retour, vous devez avoir déclaré une variable de type "pointeur vers FILE" comme la variable de flux de l'exemple ci-dessus à la ligne 9. À la ligne 13, vous pouvez voir un appel à la fonctionfopen
avec le nom de fichier statique"test"
et le mode d'ouverture"r"
: la valeur de retour est envoyé dans l'objet flux.Un flux peut être ouvert par n'importe laquelle de ces trois fonctions :
FILE *fopen( char *path, char *mode) FILE *fdopen( int fildes, char *mode) FILE *freopen( char *path, char *mode, FILE *stream)
char *path
est un pointeur vers une chaîne avec un nom de fichier dedans.char *mode
est le mode d'ouverture du fichier (voir la table ci-dessous).int fildes
est un descripteur de fichier qui est déjà ouvert et dont le mode correspond.Il est possible d'obtenir un descripteur de fichier grâce à la fonction système UNIX
open
. Veuillez noter qu'un descripteur de fichier n'est pas un pointeur vers FILE. Vous ne pouvez pas utiliserclose(stream)
, vous devez utiliserfclose(stream)
. C'est une erreur très difficile à débusquer si votre compilateur ne vous en avertit pas. Si vous êtes intéressés par les appels système Linux, tapezman 2 intro
pour une introduction aux fonctions et à ce qu'elles font.
FILE *stream
est un flux déjà existant.Ces fonctions retournent un pointeur vers un type FILE qui représente un flux e données ou un NULL de type
(FILE *)0
pour toute condition d'erreur.
fopen
est utilisée pour ouvrir le fichier donné avec le mode indiqué. C'est la fonction la plus utilisée pour ouvrir des fichiers.
fdopen
est utilisée pour assigner un flux à un descripteur de fichier déjà ouvert. Le mode du descripteur de fichier et defdopen
doivent correspondre.
freopen
est normalement utilisée pour redirigerstdin, stdout
etstderr
vers un fichier. Le flux qui est fourni sera fermé et un nouveau flux sera ouvert vers le chemin donné, avec le mode donné.Cette table montre les modes et leurs résultats.
ouverture pour tronquer créer position de mode lecture écriture fichier fichier démarrage ---- ---- ------- ------ ------ --------- "r" y n n n début "r+" y y n n début "w" n y y y début "w+" y y y y début "a" n y n y fin de fichier "a+" y y n y fin de fichierla première ligne indique que
"r"
ouvrira un flux en lecture, le flux ne sera pas ouvert en écriture, ne tronquera pas le fichier à une taille de zéro, ne créera pas le fichier s'il n'existe pas déjà et sera positionné au début du fichier.
10.2 Vidage de flux
Il arrive parfois que vous vouliez vous assurer d'avoir écrit un fichier sur le disque et qu'il n'attende pas dans un tampon. De même, vous pouvez souhaitez vous débarrasser d'entrées utilisateurs pour en avoir de nouvelles, pour un jeu par exemple. Les deux fonctions suivantes sont utiles pour vider les tampons de flux. L'une se débarrasse des données alors que l'autre les stocke de manière sûre dans le flux.
int fflush(FILE *stream) int fpurge(FILE *stream)
FILE *stream
est un flux déjà existant.Ces fonctions retournent 0 en cas de succès, EOF sinon.
fflush
est utilisée pour vider en écrivant les tampons du flux vers un périphérique ou un fichier.
fpurge
est utilisée pour vider les tampons des données non écrites ou non lues qui attendent dans un tampon. J'en parle comme d'une purge destructrice car elle vide les tampons d'écriture et de lecture en se débarrassant de leur contenu.
10.3 Fermeture de flux
Quand vous en avez fini avec un flux, vous devez tout nettoyer après votre programme. Quand vous fermez un flux, la commande assure que les tampons sont écrits avec succès et que le flux est vraiment fermé. Si vous quittez un programme sans fermer vos fichiers, il est plus que probable que les derniers octets que vous avez écrits seront là. Mais vous ne savez pas avant d'avoir vérifié. Il y a également une limite au nombre de flux qu'un processus peut ouvrir à la fois. Dès lors, si vous n'arrêtez pas d'ouvrir des flux sans fermer les vieux flux, vous allez gaspiller vos ressources système. Une seule commande est utilisée pour fermer les flux.
int fclose(FILE *stream)
FILE *stream
est un flux déjà ouvert.Retourne 0 sur un succès, EOF sinon.
fclose
vide les tampons de flux donnés et les désassocie du pointeur vers FILE.
10.4 Renommage et effacement de fichiers
Ces deux commandes fonctionnent de la même manière que
rm
etmv
mais sans les options. Elles ne sont pas récursives mais vos programmes peuvent l'être, donc faites attention de ne pas implémenter accidentellement votre propre version derm -rf /
. Par ailleurs, ne tapez pas cette commande, elle effacerait entièrement votre disque dur !!!
int remove(char *path) int rename(char *oldpath, const char *newpath)
char *path, oldpath et newpath
sont des pointeurs vers des fichiers existants.Retourne 0 sur un succès, une valeur différente de 0 sinon.
remove
fonctionne commerm
pour effacer le fichier dans la chaîne pointée par le chemin.
rename
marche commemv
pour renommer un fichier deoldpath
versnewpath
, en changeant de répertoire si nécessaire.
10.5 Fichiers temporaires
Vous pouvez créer vos propres fichiers temporaires en utilisant les fonctions suivantes :
FILE *tmpfile(void)
Cette commande retourne un pointeur vers un FILE qui est un fichier temporaire qui disparaîtra magiquement quand votre programme finira son exécution. Vous ne saurez même jamais son nom. Si la fonction échoue, elle retourne un pointeur NULL de type
(FILE *)0
.
char *tmpnam(char *string)
Cette fonction retourne un nom de fichier dans le répertoire
tmp
qui est unique, ou NULL en cas d'erreur. Tout appel additionnel efface le nom précédent : il est donc nécessaire de déplacer le fichier autre part si vous devez en connaître le nom après l'ouverture du fichier.
10.6 Tampon de flux
Normalement, un flux est géré en tampon de bloc à moins d'être connecté à un terminal tel que
stdin
oustdout
. En mode de tampon de bloc, le flux lit un certain ensemble de données et vous donne de l'entrée ce que vous lui avez demandé quand vous lui demandez. Il arrive que vous vouliez un ensemble plus ou moins important pour améliorer les performances de certains programmes. Les quatre fonctions qui suivent peuvent être utilisées en positionnant le type de tampon et la taille des tampons. Les options par défaut correspondent normalement tout à fait à l'usage que l'on veut en faire, vous n'avez donc pas à trop vous inquiéter de cela.
int setbuf( FILE *stream, char *buf); int setbuffer( FILE *stream, char *buf, size_t size); int setlinebuf( FILE *stream); int setvbuf( FILE *stream, char *buf, int mode , size_t size);
Où mode est l'un des suivants :
_IONBF
, non tamponné, la sortie est envoyée dès réception._IOLBF
, tamponné par ligne, la sortie est envoyée dès réception d'une fin de ligne._IOFBF
, complètement tamponné, la sortie n'est pas envoyée avant réception d'un nombresize
de caractères.
setbuf
est un alias poursetvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ)
setbuffer
est un alias poursetvbuf(stream, buf, buf ? _IOFBF : _IONBF, size)
setlinebuf
est un alias poursetvbuf(stream, (char *)NULL, _IOLBF, 0)
setvbuf
positionne un tampon vers un flux donné de taillesize_t
et de modemode
.
10.7 Positionnement dans un flux
Une fois un flux ouvert, vous êtes placés à une certaine position en fonction du mode d'ouverture du flux. Lors de toute écriture ou lecture, votre position augmente avec chaque caractère. Vous pouvez savoir où vous êtes dans le flux ainsi que sauter à n'importe quelle position dans le flux. Si vous écrivez un programme de base de données, vous ne voulez certainement pas lire et ignorer un million de caractères avant de trouver l'enregistrement que vous voulez ; ce que vous voulez, c'est sauter directement à l'enregistrement et commencer la lecture.
Notez que les terminaux ne peuvent pas voir leurs flux repositionnés, seuls les périphériques par blocs (comme les disques durs) le permettront.
Notez également que si vous ouvrez un fichier en écriture et que vous utilisez
fseek
pour vous déplacer de 10 000 octets, que vous écrivez un caractère et qu'ensuite vous fermez le fichier, vous n'aurez pas un fichier de 10 0001 octets. Le fichier sera bien plus petit. On appelle ça un fichiersparse
. Si vous déplacez un fichier sparse en utilisant la commandemv
, il ne changera pas de taille carmv
est seulement un changement de la structure des répertoires, pas du fichier. Si vous faites uncp
ou untar
sur un fichier sparse, il s'étendra à sa vraie taille.
int fseek( FILE *stream, long offset, int whence); long ftell( FILE *stream); void rewind( FILE *stream); int fgetpos( FILE *stream, fpos_t *pos); int fsetpos( FILE *stream, fpos_t *pos);
FILE *stream
est un pointeur vers un fichier existant.long offset
est ajouté à la position indiqué parwhence
.int whence
est soitSEEK_SET, SEEK_CUR
, ouSEEK_END
selon l'endroit où vous voulez appliquer l'offset : le début, la position courante ou la fin.fpos_t *pos
est un indicateur de position dans un fichier complexe.Sur certains systèmes, vous devez utiliser ceci pour récupérer et positionner les positions dans le flux.
Si ces fonctions s'exécutent avec succès,
fgetpos, fseek, fsetpos
retournent 0,ftell
retourne l'offset courant. Autrement, EOF est renvoyé. Il n'y a pas de valeur de retour pourrewind
.
fseek
affecte la position dans le fichier du flux à la valeur de l'offset plus la position indiquée parwhence
, que ce soit le début, la position courante ou la fin. Cela est utile pour la lecture linéaire, l'ajout à la fin d'un flux et revenir au point de lecture que vous avez quitté.
ftell
retourne la position courante du flux.
rewind
met la position courante au début du flux. Veuillez noter qu'aucun code d'erreur n'est retourné. Cette fonction est sensée toujours bien s'exécuter.
fgetpos
est utilisé commeftell
pour retourner la position du flux. Cette position est retournée dans la variablepos
qui est du typefpos_t
.
fsetpos
est utilisé commefsee
k dans la mesure où il va mettre la position courante dans le flux à la valeur dans pos.Sur certains systèmes, vous devez utiliser
fgetpos
etfsetpos
pour positionner de manière fiable votre flux.
10.8 Codes d'erreur
Quand l'une des fonctions ci-dessus retourne une erreur, vous pouvez voir ce que l'erreur était et même obtenir un message d'erreur à afficher à l'utilisateur. Il existe un groupe de fonctions qui traitent les valeurs d'erreur. Il suffit, pour le moment, de reconnaître les erreurs et arrêter l'exécution.
Cependant, si vous écrivez un traitement de texte avec une belle interface graphique, vous ne souhaitez certainement pas qu'il s'arrête à chaque fois que vous ne pouvez pas ouvrir un fichier. Vous voulez plutôt afficher le message d'erreur à l'utilisateur et continuer. Dans un article futur, je parlerai des fonctions d'erreur ou alors quelqu'un peut les résumer pour nous et envoyer un article accompagné d'un code source commenté pour nous montrer comment cela fonctionne.
Si quelqu'un est intéressé, ces fonctions sont
clearerr, feof, ferror
etfileno
.
10.9 Conclusion
Voilà, c'est tout pour ce mois-ci. J'ai beaucoup appris et j'espère que vous aussi. La plus grande part de ces informations est disponible à travers les pages de man mais les dates qui figurent sur celles-ci datent de 4 ans. Si quelqu'un dispose de mises à jour de celles ci, veuillez me les envoyer et je me corrigerai dans les articles futurs.
Le mois prochain, je parlerai des entrées/sorties. Je prendrai le programme listé plus haut et y ajouterais quelques fonctionnalités pour ajouter une colonne de nombres et sortir le résultat vers la sortie standard. Ce programme d'exemple pourra faire quelque chose d'utile.
10.10 Bibliographie :
- The ANSI C Programming Language, Second Edition, Brian W. Kernighan, Dennis M. Ritchie, Printice Hall Software Series, 1988
- The Standard C Library, P. J. Plauger, Printice Hall P T R, 1992
- The Standard C Library, Parts 1, 2, and 3, Chuck Allison, C/C++ Users Journal, January, February, March 1995
- STDIO(3), BSD MANPAGE, Linux Programmer's Manual, 29 November 1993
- Unidentified File Objects, Lisa Lees, Sys Admin, July/August 1995
- A Conversation With William Kahan, Jack Woehr, Dr Dobb's Journal, November 1997
- Java and Client-Server, Joe Novosel, Linux Journal, January 1997
Copyright © 1998, James Rogers - Publié dans le Numéro 24 de la Linux Gazette
Adaptation française de Pierre Tane
Page suivante Page précédente Table des matières