La lecture en ligne est gratuite
Télécharger

Publications similaires

CHAPITRE 19Le langag
Librairies
e C
287
einev
Télécommunications
19.1L’intérêt des librairies
mjn
La librairie standard de UNIX est actuellement encore largement utilisée. Mais il ne faut pas oublier qu'elle a été prévue pour une utilisation dans un cadre de programmation système. Elle recèle nombre de fonctions de très bas niveau, ce qui, incidemment, en fait un outil très performant dans le cadre de la programmation système ou la programmation de microcontrô-leurs. Ainsi des routines couramment utilisées sous UNIX, commesignal(),setjmp(), longjmp()rupture du déroulement normal du programme qui peut poser, provoquent une de sérieux problèmes de récupération de mémoire, en particulier pour des objets crées auto-matiquement, de manière pas toujours transparente par le compilateur. En principe, lorsqu'un groupe de travail se met à développer en C, il se met sur pied une librairie de procédures qui va croître au cours du temps et au cours des projets. Cette démarche nécessite une organisation, des règles à respecter absolument. Ces règles sont fonction de la dimension du groupe de travail. Pour de petits groupes largement autonomes (10 personnes), on peut se contenter d'encourager les gens à parler entre eux. Si cette communication ne va pas de soi, souvent en raison de problèmes d’incompatibilité entre les divers développeurs, on peut "organiser" cet échange de vues, sans que cela soit forcément une directive émise par un chef de groupe donné. En fait, une directive fonctionne généralement mieux lorsqu'elle est spontanément appliquée. Une bonne méthode (et de surcroît facile à rendre populaire dans nos contrées) est l'organisation fréquente de verrées ou chacun parle de manière libre de ce qu'il fait. Pour des groupes de développement plus importants, ou des projets regroupant plusieurs groupes (parfois distribués géographiquement), il n'est plus possible de se contenter d'une communication orale, et il faut recourir à des documents pour échanger les informations con-cernant les classes produites. La production de documents étant une entreprise longue et fas-tidieuse, on peut raisonnablement craindre que l'échange d'informations ne se fasse pas aussi bien que l'on pourrait le souhaiter. C'est pourquoi là aussi, il est important de trouver des moyens permettant de favoriser des communications informelles entre les groupes de déve-loppement. Ces communications ont de tous temps été précieuses, elles le sont d'autant plus lorsque l'on dispose d'un outil potentiellement efficace pour échanger du code. La création de modules de librairies, dans ce contexte, doit faire l'objet de soins particu-liers par les auteurs de code. Exporter des classes boguées n'apporte que des ennuis, tant à l'auteur qu'aux utilisateurs. Dans le meilleur des cas, on n'utilisera plus les classes développées par un auteur particulier, parce que trop souvent boguées. Dans le pire des cas, l'exportation d'une erreur dans plusieurs projets n'ayant en commun que la réutilisation d'un module parti-culier peut causer de graves problèmes financiers à une société. Lors du développement d'une classe, il est prudent de partir du principe que l'utilisateur est un ennemi potentiel, contre le-quel il convient de se protéger. Si il existe une condition qui peut entraîner une erreur dans le code d'une classe, il est plus que vraisemblable qu'un utilisateur de cette classe déclenchera un jour ou l'autre cette condition. Une classe que l'on désire exporter doit donc impérativement être à l'épreuve de toute manipulation dont on sait qu'elle est dangereuse. Il vaut mieux avorter brutalement l'exécution d'un programme avec un message d'erreur sur la console que de laisser se poursuivre une exécution dans une situation d'erreur.
288
Le langage C
einev
Le langa
Télécommunications
Le meilleur outil de développement que l'on puisse imaginer, c' est du code fiable, déjà testé 
ge C
mjn
289
einev
19.2ts C eirdradnabraiLi
Télécommunications
mjn
La librairie C standard est certainement la plus utilisée des librairies C++, bien qu’elle ne soit pas “orientée objets”. Le principal avantage de cette librairie est qu’elle est en partie normalisée par l’ANSI, et de ce fait se retrouve sur toutes les implémentations ANSI du lan-gage. La liste présentée dans le cadre de ce chapitre ne prétend pas être exhaustive, mais pré-sente les principales fonctions de la librairie C telle qu’on la retrouve sur quasiment toutes les implémentations de C et de C++ (à l’exception de certains compilateurs C de bas niveau, uti-lisés pour la programmation de chips spécialisés, comme des microcontrôleurs et des proces-seurs de signaux, ou DSP). Il est important de se souvenir que cette librairie a été conçue indépendemment de C++. Elle peut donc présenter de sérieuses incompatibilités avec certaines constructions plus mo-dernes de C++. En pratique, ces incompatibilités ne sont pas gênantes, dans la mesure où l’on ne mélange pas les fonctions de la librairie C avec les fonctions correspondantes d’une libraire C++, ou pire, avec des instructions réservées de C++. Ainsi, le code suivant produira à coup sûr de sérieuses difficultés à l’utilisation : const int nbPoint = 200;  struct Point { int x, y; }; Point *pptr; pptr = calloc(nbPoint, sizeof(Point)); ... delete pptr; // suicidaire !
19.2.1Généralités (#include>h<stdarg.) void va start(va list varList,parameter); _ _ typeva arg(va list varList,type); _ _ _ _ void va end(va list varList); Ces macros permettent de définir des fonctions avec des listes d’arguments de longueur variable. Elles sont spécialement utilisées pour des fonctions telles que printf() et scanf() entre autres, mais il est également possible de définir des fonctions utilisant un nombre variable d’arguments à des fins personnelles. La fonction suivante effectue la moyenne d’un nombre arbitraire de valeurs réellespositives: #include <stdarg.h> double mean(float firstArg, ...) { va list argList; _ double nextArg; double mean; int count;
290
Le langage C
einev
Télécommunications
mjn
// Indiquer quel est le premier paramètre de la liste, // et initialiser les macros : _ va start(argList, firstArg); mean firstArg; = next = 0; // Une valeur négative sert de terminateur de la liste _ while ((next = va arg(argList, double)) >= 0) { mean += next;   count++; } // Ne pas oublier va end !!! _ _ va end(argList); return (mean / count); } void main() { printf(“Resultat = %f\n“, mean(1.5, 2.3, 4.2, 5, 6, 2.1)); } Ces macros sont implémentées dans pratiquement toutes les implémentations de C ( en tout cas, toutes les implémentations compatibles avec le standard ANSI). Par l’utilisation de classes en C++, il existe toutefois des méthodes autrement puissantes - et autrement plus élé-gantes- de résoudre ce genre de problèmes, si bien que nous n’insisterons pas plus longtemps sur cette possibilité.
19.2.2stdio (#include <stdio.h>) On connaît déjàstdiopar le biais deprintf()etscanf(). En principe, on con-seille plutôt l’utilisation de iostream pour l’accès à des fichiers en C++. En pratique,stdio reste largement utilisé, souvent pour les entrées-sorties sur terminal. De plus, l’utilisation de stdiopar diverses librairies existantes est intensive, si bien qu’il vaut la peine de s’intéres-ser quand même à cette librairie. Dans le cadre de stdio, les conventions suivantes sont utilisées pour définir le modes d’accès aux fichiers :  Le fichier est utilisable en lecture seulement. Pour l’ouvrir, le fichier doit (Read)“r” : exister au préalable. en écriture seulement. Ouvrir un fichier existant en mode“w” : (Write) Fichier utilisable “w” surécrit le contenu précédent de ce fichier. Si le fichier n’existait pas lors de l’ouver-ture, il sera crée. : (Append) Ouverture du fichier en mode ajout. Les données sont appondues à la fin“a” du fichier, mais ce dernier doit exister au moment de l’ouverture.
Le langage C
291
einev
Télécommunications
mjn
“r+” : (Read and Update) Ouverture d’un fichierexistanten mode lecture / écriture. d’un fichier en mode lecture / écriture. Si ce fichier“w+”, “rw” : (Read / write) Ouverture n’existe pas, alors il est crée. Create) Comme “a”, mais si le fichier n’existe pas, alors il est crée.“a+” : (Append / La lettre “b”, ajoutée à une autre indication (comme par exemple “wb+”) indique qu’il s’agit d’un fichier binaire. Utilisée dans le cadre de systèmes comme MVS, VMS, etc.., cette indication est rarement utilisée dans le cadre des systèmes de gestion de fichiers modernes. Toutes les routines retournent un entier indiquant le résultat -couronné de succès ou non- de l’appel à la fonction concernée. On peut choisir de négliger ce résultat ou non. En rè-gle générale, on testera le résultat de l’ouverture de fichier, et le programmeur choisira fré-quemment de négliger les résultats livrés par les autres opérations. Cette manière de faire est courante dans tous les langages, et dangereuse dans tous les langages. Dans le cadre des petits exemples donnés avec les procédures, nous négligerons fréquemment de tester le résultat de l’opération, ceci afin de ne pas surcharger l’exemple. En cas d’erreur, la cause de l’erreur peut être déduite de la variableerrno. Les valeurs possibles deerrnosont explicitées dans le fichiererrno.h. FILE *fopen(const char* fileName, const char* mode); Ouverture d’un fichier du nom defileNameen modemode. Le résultat de l’appel est un pointeur sur une variable d’un type en principe opaque, qui sera passé ultérieurement aux autres fonctions de manipulation de fichiers. Si l’ouverture échoue, c’est une valeur de poin-teur NULL qui sera retournée. FILE* fichier; fichier = fopen(“porno.gif”, “r”); if (fichier != NULL) { ... // Considérations anatomiques ? ... } FILE *freopen(const char* fileName, const char* mode, FILE* stream); Ferme le fichierstreamlieu et place le fichier nommé paret ouvre en fileNamedans le modemode. En cas d’échec de l’ouverture, un pointeur NULL est retourné, mais l’état di fichier ouvert précedemment est indeterminé (généralement, il a été fermé). La valeur de la variableerrnoune indication quant à ce qu’il s’est effectivement passé.peut donner FILE* fichier; fichier = freopen(“anOtherStdout”, “w”, stdout); // redirection de stdout sur un fichier... if (fichier == NULL) { printf(“Redirection de stdout manquée pour cause de %d\n”, strerror(errno));
292
Le langage C
einev
Télécommunications
mjn
exit(1); } // else continuer allègrement le travail... int fclose(FILE* stream); Fermeture du fichier associé au pointeur stream. Rappelons qu’en C, un fichier n’est pas fermé implicitement lors de la destruction du pointeur fichier associé. Retourne 0 si suc-cès, EOF dans le cas contraire.fclose()provoque un appel implicite àfflush()pour vider le tampon associé à stream. FILE* f = fopen(“tmp.tmp”, “w”); fprintf(f, “Contenu de fichier bidon\n”); fclose(f); int fflush(FILE *stream) Provoque l’écriture forcée du tampon associé au fichier stream. En cas de succès, re-tourne 0, EOF en cas d’échec. Si stream vaut NULL, tous les fichiers ouverts en écriture se-ront vidés. printf(“Ecriture dans le tampon...”); if ((err = fflush(stdout))) { printf(“fflush n’a pas fonctionné pour cause %d\n”, errno); exit(0); }  // Provoque l’écriture forcée, malgré // l’absence de \n int remove(const char* name); Destruction du fichier poratnt le nomname. Retourne 0 en cas de succès. Si le fichier portant le nom name était ouvert au moment de l’appel de remove(), le résultat dépend de l’implémentation (en particulier du système d’exploitation), mais généralement, cette situa-tion est signalée comme une erreur. int rename(const char* oldName, const char* newName); Permet de renommer le fichier appeléoldNameennewName.Cette opération échoue si le fichier désigné par newName existe déjà (pas vrai pour toutes les implémentations). On trouve parfois égalementrename(const char* oldName, int ref, const char* newName) comme définition. ref indique alors le volume sur lequel doit être effec-tuée cette opération. FILE* f = fopen(“tmp.tmp”, “w”); fprintf(f, “Contenu de fichier bidon\n”); fclose(f); rename(“tmp.tmp”, “bidon.txt”); f = fopen(“bidon.txt”, “r”); char buffer[200];
Le langage C
293
einev
Télécommunications
mjn
fscanf(f, “%s”, buffer); printf(“%s”, buffer); fclose(f); remove(bidon.txt”); _ int setvbuf(FILE* stream, char *buf, int mode, size t size); setvbuf() permet de définir comment les entrées-sorties seront mises en tampon, et quel-le sera la taille du tampon. Cet appel doit faire suite immédiatement à l’appel de fopen(), donc avant toute autre utilisation du fichier, sans quoi les résultats peuvent devenir imprévisibles. stream pointe sur le descripteur de fichier pour lequel on définit le tampon, buf, pointe sur un tampon mémoire de taille size, et mode peut prendre l’une des trois valeurs suivantes : _IOFBF (Full BuFfering) _ (Line ng) IOLBF BuFferi _IONBF (No BuFfering) A noter que beaucoup d’implémentations de librairies offrent la possibilité d’effectuer cet appel implicitement, de manière combinée à l’appel à fopen(), si bien que l’appel explicite n’est plus nécessaire. Cette fonction tend de ce fait à ne plus être utilisée, sauf dans des cas particuliers où l’on désire forcer une dimension de tampon très grande pour des fichiers de très grande taille. Cette fonction retourne 0 en cas de succès. Un appel à cette fonction après une opération sur le fichier (lecture ou écriture) a un résultat indéfini. void setbuf(FILE* stream, char *buf); L’appel de cette fonction correspond à appel _ er setvbuf en mode IONBF, avec size à 0 si buf est NULL. Si buf n’est pas NULL, il doit avoir au préalable été défini à la librairie par un appel dépendant de l’implémentation. La longueur du buffer, en particulier correspondra à la longueur par défaut du tampon fichier (souvent un multiple d’un bloc disque). FILE* tmpfile(void); Crée un fichier temporaire qui sera automatiquement détruit lors de la fermeture de ce fichier, ou à la fin du programme (dans la mesure où le programme peut se terminer normale-ment, bien sûr). Le fichier sera ouvert en mode “wb+”. char* tmpnam(char* name); Génère un nom de fichier correct par rapport au système d’exploitation, et unique dans l’environnement d’exécution. Sinameest différent de NULL, le nom généré sera écrit dans la chaîne fournie en paramètre. La longueur minimale de cette chaîne dépend de l’implémen-tation (en particulier du système d’exploitation considéré). Le nombre maximum de noms dif-férents que peut générertmpnam()dépend de l’implémentation, mais devrait dans tous les cas être supérieur à 9999. Sinamefaut se référer à l’implémentation pour savoir comment gérer leest NULL, il pointeur retourné partmpnam(). Certaines implémentations (la plupart, en fait) utilisent une chaîne de caractères statique qui sera réutilisée à chaque appel detmpnam(), l’ancien conte-
294Le langage C
einev
Télécommunications
mjn
nu se trouvant dés lors surécrit. Plus rarement, une nouvelle chaîne sera générée en mémoire, par un appel àmalloc()dans ce cas, il faudra que le programme appelant prenne en charge; la destruction de cette chaîne par un appel àfree(). int printf(const char* controlString, ...); printfécrit une chaîne de caractères formattée sur la sortie standard (stdout). La formattage est défini par la chaîne de caractères controlString, alors que les arguments sont contenus dans une liste de longueur indéfinie. Un descripteur de format est indiqué par le ca-ractère % suivi d’un caractère indiquant la conversion à effectuer. Ainsi, la séquence %d in-dique qu’il faut interpréter l’argument correspondant (défini par sa position dans controlString et dans la liste d’arguments) comme un entier décimal. En cas de succès,printfretourne le nombre de caractères imprimés surstdout, si-non, EOF. printf(“La valeur de pi est %7.5f\n”, 3.1415926); imprime sur la sortie standard : La valeur de pi est 3.14159 Autres références : VoirDescripteurs de formats de printf(), page30. int scanf(const char* controlString, ...); scanf convertit des entrées en provenance de l’entrée standard stdin en une série de va-leurs, en se servant de controlString comme modèle de conversion. Les valeurs sont stockées dans les variables sur lesquelles pointe la liste d’arguments indiquée par ... dans le prototype. La chaîne controlString contient des descriptions de format qui indiquent à scanf comment il convient de convertir les entrées de l’utilisateur. Un descripteur de format est indiqué par le caractère % suivi d’un caractère indiquant la conversion à effectuer. Ainsi, la séquence %d indique qu’il faut interpréter les entrées sur stdion comme un entier décimal. scanf retourne le nombre d’arguments auxquels il a été possible d’assigner une valeur. Ce nombre peut être différent du nombre d’arguments passé à scanf! Si il n’est pas possible de lire sur stdin, EOF sera retourné. int x; printf(“Entrer une valeur entière: “); scanf(“%d”, &x); printf(“Vous avez entré %d\n”, x); Autres références : VoirDescripteurs de formats de printf(), page30.
int fprintf(FILE* f, const char* controlString, ...); Fonctionnalité équivalente àprintf(). De fait,printf() peut également s’écrire
Le langage C
295
einev
Télécommunications
fprintf(stdout, “Valeur de pi = %f\n”, 3.1415926);
mjn
int fscanf(FILE *f, const char* controlString, ...) Fonctinnalité équivalente àscanf(). De fait,scanf() peut également s’écrire float unReel; print(“Entrer un nombre reel “); fscanf(stdout, “%f”, unReel); int sprintf(char *buffer, const char* controlString, ...); Fonctionnalité équivalente àfprintfsortie se fait dans une chaîne de ca-(), mais la ractères en lieu et place d’un fichier. L’utilisateur devra veiller lui-même à réserver une place suffisante dans la chaîne référencée par *buffer. int fgetc(FILE *stream); Lecture du prochain caractère dans le fichier associé au pointeur stream. Le fait que le résultat soit entier (au lieu de char) est dû à la possibilité de livrer EOF (de type int) en cas d’erreur. Pratiquement, du fait de la conversion implicite int <-> char, ceci n’a pas vraiment dimportance. Valeurs de retour : le prochain caractère si l’opération est couronnée de succès. EOF (négatif, en général) si il y a une erreur
char* fgets(char* str, int nbChars, FILE *stream); Lecture d’une chaîne de caractères du fichier associé au pointeurstreamdans la zone mémoire pointée parstr. str doit pointer sur une zone mémoire de dimension au moins égale à nbChars + 1 (+ 1 en raison du caractère \0 qui termine une chaîne de caractères en C). C’est au programmeur de s’assurer que la place qu’il a reservée est suffisante. La fonctionfgets()lit des caractères jusqu’à un caractère de fin de ligne, ou jusqu’à la lecture de nbChars caractères. Valeurs de retour : strsi l’opération est couronnée de succès. NULL si il y a une erreur. int fputc(int c, FILE* stream); Ecriture du caractère c (donné sous forme d’entier) dans le fichier associé au pointeur stream. Retourne la valeur entière de c en cas de succès, EOF autrement. On ne peut donc pas sans autre écrire EOF au moyen defputc().
296
Le langage C
einev
Télécommunications
mjn
// Copie d’un fichier dans un autre : FILE* source; FILE* copy; int x; if ((source = fopen(“source.dat”, “r”)) == NULL) { printf(“Erreur à l’ouverture de source.dat, %s\n”, strerror(errno)); exit(1); } if ((source = fopen(“copy.dat”, “w”)) == NULL) { printf(“Erreur à l’ouverture de copy.dat, %s\n”, strerror(errno)); exit(1); } while ((x = fgetc(source)) != EOF) fputc(x); fclose(source); fclose(copy); int fputs(const char* str, FILE* stream); Ecrit la chaîne de caractères pointée par str dans le fichier pointé par stream. Retourne 0 en cas de succès, EOF en cas d’échec. int getc(FILE* stream); int putc(int c, FILE* stream); Implémentations sous forme de macros defgetc()etfputc(). Comportement en principe similaire. int puts(const char* str); Identique àfputs(), mais écriture sur stdout. char* gets(char* str); Identique àfgets(), mais lecture à partir de stdin. int getchar(void) Retourne la valeur du prochain caractère, sous forme d’un entier, lu sur stdin. Retourne EOF en cas d’erreur. int putchar(int car); Ecrit le caractèrecar(donné sous forme d’entier) sur stdout. Retourne car en cas de suc-cès, EOF en cas d’erreur. int ungetc(int c, FILE* stream); Réinsère le caractère c dans le fichier associé au pointeur stream. Retourne c en cas de
Le langage C
297