Fonctions de base en langage C/stdio.h

Début de la boite de navigation du chapitre
stdio.h
Icône de la faculté
Chapitre no 5
Leçon : Fonctions de base en langage C
Chap. préc. :math.h
Chap. suiv. :stdlib.h
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Fonctions de base en langage C : stdio.h
Fonctions de base en langage C/stdio.h
 », n'a pu être restituée correctement ci-dessus.

Description modifier

Le fichier en-tête stdio.h contient les déclarations d'un ensemble de fonctions qui gèrent les entrées/sorties des programmes écrits en C. On y trouve aussi des constantes utiles pour rendre les programmes portables : FILENAME_MAX, BUFSIZ, EOF..., des fonctions pour renommer, détruire des fichiers et créer des fichiers temporaires.

Les fonctions d'entrée/sortie sont basées sur des mécanismes complexes.

Les fonctions d'entrées sorties modifier

Les fonctions d'entrées sorties reposent sur une structure FILE remplie par la fonction fopen. Dans la suite, FP désignera un pointeur sur une structure FILE (file handle en anglais) associée à une source ou une destination de données liée à un fichier sur disque dur ou sur un autre périphérique. On parle aussi de flux (stream en anglais) de données associé à un fichier externe.

  Pour la syntaxe des fonctions d'entrée sortie, voir le wikilivre Programmation C, chapitre sur les entrées/sorties.

Bufferisation modifier

Les fonctions d'entrée/sortie contrairement aux fonctions systèmes comme open, read et write, sont dîtes bufferisée : Les données peuvent être transmises par blocs.

Elles apportent aux programmeurs une gestion automatique de mémoires tampons qui évitent de faire à chaque demande d'entrées/sorties des accès aux périphériques très coûteux en temps.

Les fonctions de la famille de setvbuf permettent de contrôler précisément ce fonctionnement pour optimiser un programme. les fonctions fflush et fpurge permettent de vider ou d’effacer les mémoires tampons à tout moment.

Flux associés par défaut à un programme modifier

Des constantes de type pointeur sur FILE sont ouverts automatiquement :

  • stdin (entrée standard) pointe sur le buffer du clavier
  • stdout (sortie standard) pointe sur le buffer de l'écran
  • stderr (sortie des erreurs) pointe sur la sortie des erreurs associée elle aussi à l'écran. Ce flux provoque une écriture immédiate.

Des fonctions spécifiques sont fournies pour les manipuler :

  • on obtient leur nom en supprimant le f de départ des noms des fonctions d'entrée sortie sur fichier.
  • on ne leur passe pas de paramètre FP (pointeur sur structure FILE *).
  • exemple : printf(...) pour fprintf(stdout, ...), scanf(...) pour fscanf(stdin,...), ...
  • Attention : certaines fonctions spécifiques ne se comportent pas comme leurs homologues :
    • putchar(c) pour fputc(c, stdout);
    • puts(str) ajoute un saut de ligne, contrairement à fputs(str, stdout);
    • gets(str) beaucoup moins sûre que fgets(str, size, stdin) (risque de débordement de str).

Exemple simple d'écriture de texte dans un fichier modifier

Le programme suivant ecritFichier.c écrit une chaine de caractères dans un fichier. Voir ses commentaires pour plus d'explications.

/*
Nom : ecritFichier.c
Role : Écrit Bonjour tout le monde ! dans le fichier bonjour.txt.
Paramètres : non pris en compte.
Code retour : 0 (EXIT_SUCCESS) ou EXIT_FAILURE en cas de problème
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -std=c99 -o ecritFichier.exe ecritFichier.c
Pour exécuter, tapez : ./ecritFichier.exe puis afficher le contenu de bonjour.txt
*/
#include <stdio.h>
#include <stdlib.h>
#define NOM_FIC "bonjour.txt"

int main(void)
{
	// Declaration du descripteur de fichier
	FILE *hFile = NULL;
	int codeRetour = 0;
 
	// Ouvre le fichier bonjour.txt et teste
	hFile = fopen(NOM_FIC, "w");
	if (hFile == NULL)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible d'ouvrir %s en écriture\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	// Écrire Bonjour tout le monde, dans le fichier
	codeRetour = fputs("Bonjour tout le monde !\n", hFile);
	if (codeRetour == EOF)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible d'ecrire dans %s\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	// Ferme le fichier
	codeRetour = fclose(hFile);
	if (codeRetour == EOF)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible de fermer correctement %s\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	(void)printf("texte écrit dans %s, sortie normale\n",
		NOM_FIC);
 
	return EXIT_SUCCESS;
} // int main(...

Codes Retours modifier

Pour obtenir un programme robuste, il faut impérativement tester les codes retours des fonctions d'entrée/sortie et agir en conséquence : informer l'utilisateur, entreprendre un autre traitement, arrêter le programme.

  • Les tests if (hFile == NULL) et if (codeRetour == EOF) sont nécessaires, mais pas suffisants : l'appel à la fonction perror() permet d'afficher la cause de l'erreur codée dans la variable globale errno sur stderr. Il faut appeler au plus tôt perror après l'erreur, car tout appel système ultérieur modifiera errno en cas de problème.
  • Par exemple, si le fichier bonjour.txt n’est pas accessible en écriture, on obtient :
MacMini-TM:~/Documents/developpement/c thierry$ ./ecritFichier.exe 
Erreur: Permission denied
Impossible d'ouvrir bonjour.txt en écriture
  • Les causes d'erreur peuvent être nombreuses : fichier non accessible, espace disque insuffisant... :Pour les connaître, consultez le chapitre ERRORS des pages de man des fonctions de niveau 3, exemple : man fputs.
ERRORS
     [EBADF]            The stream argument is not a writable stream.

     The functions fputs() and puts() may also fail and set errno for any of
     the errors specified for the routines write(2).
On voit ici, qu’il faut aussi regarder la page de man de la fonction de niveau 2 (système) write : man -s2 write.
  • Les constantes symboliques correspondant aux codes d'erreur sont dans errno.h
  • Utilisation de la fonction c99 strerror :
// Pour strerror
#include <string.h>
// Pour variable errno
#include <errno.h>
// ...
	if (hFile == NULL)
	{
		char * chErreur = strerror(errno);
		(void)fprintf(stderr,
			"Impossible d'ouvrir %s en écriture\n"
			"Erreur système : %s\n",
			NOM_FIC, chErreur);
		exit(EXIT_FAILURE);
	}
// ...
Il faut sauver ou exploiter errno au plus vite après l'erreur.

Exemple simple de lecture de texte dans un fichier modifier

/*
Nom : litFichier.c
Role : Lit et affiche la chaine de caractère lue dans le fichier bonjour.txt.
Paramètres : non pris en compte.
Code retour : 0 (EXIT_SUCCESS) ou EXIT_FAILURE en cas de problème
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -std=c99 -o litFichier.exe litFichier.c
Pour exécuter, tapez : ./litFichier.exe puis afficher le contenu de bonjour.txt
*/
#include <stdio.h>
#include <stdlib.h>
// Pour constante _POSIX_MAX_INPUT
#include <limits.h>
#define NOM_FIC "bonjour.txt"
 
int main(void)
{
        // Declaration du descripteur de fichier
        FILE *hFile = NULL;
        int codeRetour = 0;
		char chaineLue[_POSIX_MAX_INPUT];
 
        // Ouvre le fichier bonjour.txt en lecture et teste
        hFile = fopen(NOM_FIC, "r");
        if (hFile == NULL)
        {
                perror("Erreur");
                (void)fprintf(stderr,
                        "Impossible d'ouvrir %s en lecture\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        // Tentative de lecture dans le fichier
	if (fgets(chaineLue, _POSIX_MAX_INPUT, hFile) != NULL)
	{
		(void)printf("Chaine lue : %s", chaineLue);
	}
	else if (feof(hFile) != 0)
        {
                (void)fprintf(stderr,
                        "Rien à lire dans %s : fichier vide.\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
	else if (ferror(hFile) != 0)
        {
                perror("Erreur"); // ferror et feof ne modifient pas errno
                (void)fprintf(stderr,
                        "Problème de lecture dans %s.\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        // Ferme le fichier
        codeRetour = fclose(hFile);
        if (codeRetour == EOF)
        {
                perror("Erreur");
                (void)fprintf(stderr,
                        "Impossible de fermer correctement %s\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        return EXIT_SUCCESS;
} // int main(...

Erreurs fréquentes et recommandations modifier

  • Lors de l’utilisation de printf, une erreur d'exécution fréquente est due à la non concordance en nombre ou type entre les variables à traiter et les spécificateurs de format.
  • Une erreur grave consiste à passer aux fonctions de la famille scanf une valeur au lieu d'une adresse.
  • Il est préférable d’utiliser les fonctions de lecture comme fgets, snprintf qui permettent de spécifier la taille maximale de de la chaine recevant la lecture pour éviter les débordements.
  • Les fonctions fwrite et fread permettent respectivement d'écrire et de lire des données binaires dans un flux. Ce flux doit être ouvert à l'aide de la fonction fopen en spécifiant le caractère b (rb, rb+, wb) pour l'attribut mode : sa présence indique (pour certains systèmes comme MS-DOS) qu’il s'agit d'un flux binaire et non d'un flux caractère. Les données lues ne devront pas être modifiées (caractère ASCII 0xA (\n) transformé en 0xAD (\n\r)). Pour la portabilité de vos programmes qui écrivent ou lisent des données binaires, ne pas l'oublier!
    • La relecture de données binaire sur une même machine ne pose pas de problèmes. Sur des machines différentes, l'exercice pourra être plus difficile : problème d'ordre des octets : little/big endian, taille des types différents.
    • La solution pourrait être :
      • d’utiliser les fonctions ASCII du chapitre précédent si les volumes sont faibles.
      • d'appeler les fonctions d'une bibliothèque spécialisée
      • de greffer à votre code un compresseur de données genre gzip si les données sont volumineuses.
  • Les fonctions de positionnement dans un fichier permettent de travailler sur disque avec un volume de données en mémoire minimum, mais les temps d'exécution peuvent augmenter.

Création de fichiers temporaires modifier

Rôle modifier

Les fichiers temporaires servent aux programmes à stocker sur disque des informations qu’il pourra réutiliser plus tard lors de son exécution. Cette manœuvre peut permettre :

  • de traiter les informations un peu plus tard par une fonction séparée.
  • d'économiser de la place en mémoire vive en stockant sur disque, par exemple, des informations relatives à une tâche qui devient moins prioritaire.

tmpfile() modifier

La fonction tmpfile() de stdio.h permet de créer en lecture écriture un fichier qui sera automatiquement effacé lorsqu’il sera fermé. Il sera créé dans un répertoire spécial dont le nom se trouve dans P_tmpdir ou par exemple dans /tmp ou \temp.

Le programme suivant écrit puis relit dix entiers dans un fichier temporaire :

/***************************************************************************
Nom ......... : tmpfile.c
Role ........ : Exemple d’utilisation de la fonction tmpfile
Auteur ...... : Thierry46, licence GNU GPL
Date creation : 7/4/2008

- Compilation et édition de liens :
gcc -Wall -pedantic -o tmpfile.exe tmpfile.c
- Exécution : ./tmpfile.exe
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define TAILLE_BUF 255

int main (void)
   {
   FILE *h_ftemp;
   unsigned int i;
   int nombre;
   char buffer[TAILLE_BUF];
   
   (void)printf("P_tmpdir = %s\n", P_tmpdir);
   /* Ouverture du fichier temporaire */
   (void)puts("Ouverture du fichier temporaire par tmpfile().");
   if ( (h_ftemp = tmpfile()) == NULL )
   {
      perror("Erreur sur tmpfile()");
      exit(EXIT_FAILURE);
   }
   
   /* Rangement dans ce fichier de 10 nombres aleatoires a 1 chiffre */
   (void)puts("Rangement dans ce fichier de 10 nombres aleatoires :");
   for ( i = 0; i < 10; i++)
   {
      nombre = rand() % 10;
      (void)printf("%d\n", nombre);
      (void)fprintf(h_ftemp, "%d\n", nombre);
   }
   
   /* Relecture des nombres aleatoires ranges dans le fichier */
   (void)puts("Relecture des nombres aleatoires ranges dans le fichier :");
   rewind(h_ftemp);
   while ( fgets(buffer, TAILLE_BUF, h_ftemp) != NULL )
      (void)fputs(buffer, stdout); /* Evite l'ajout d'un \n supplémentaire */
   
   (void)puts("Fin du programme tmpfile,\n"
      "le fichier temporaire est detruit automatiquement.");
   return EXIT_SUCCESS;
}

tmpnam() modifier

Cette fonction de prototype char *tmpnam(char *str) retourne un nom de fichier temporaire mais ne l'ouvre pas comme tmpfile(). C'est à vous d'ouvrir le ficher et de le détruire. Le nom créé débute par le contenu de la variable P_tmpdir de stdio.h. Vous lui passez en paramètre :

  • NULL : alors le nom sera généré dans une zone statique de tmpnam et pourrait être écraser lors d'un prochain appel.
  • Soit une chaîne de caractère que vous aurez définie de longueur au moins L_tmpnam.

Limite : tmpnam retourne une chaîne de caractères différente pour TMP_MAX appels successifs. TMP_MAX, défini dans stdio.h vaut au minimum 25, pour un système Mac OS X.4 : 308915776.

Exemple d’utilisation :

/***************************************************************************
Nom ......... : tmpnam.c
Role ........ : Exemple d’utilisation de la fonction tmpnam
Auteur ...... : Thierry46, licence GNU GPL
Date creation : 7/4/2007

Pour compilation et édition de liens :
gcc -Wall -pedantic -o test_tmpnam.exe tmpnam.c
Pour exétuter : ./test_tmpnam.exe
***************************************************************************/
#include <stdio.h>
#include <stdlib.h> /* Pour constantes EXIT_ */
#define TAILLE_BUF 255

int main (void)
{
   FILE *h_ftemp;
   char nomfic[L_tmpnam];
   unsigned int i;
   int nombre;
   char buffer[TAILLE_BUF];
   
   (void)printf("TMP_MAX = %d\n", TMP_MAX);

   /* Creation du nom du fichier temporaire */
   (void)puts("Creation du nom du fichier temporaire par tmpnam() :");
   if ( tmpnam(nomfic) == NULL )
   {
      perror("Erreur sur tmpnam()");
      exit(EXIT_FAILURE);
   }
   (void)puts(nomfic);

   /*
   Ouverture du fichier temporaire :
   Le fichier cree sera sous notre responsabilite :
   fermeture, destruction...
   */
   (void)puts("Ouverture du fichier temporaire.");
   if ( (h_ftemp = fopen(nomfic, "w")) == NULL )
   {
      perror("Erreur sur fopen()");
      exit(EXIT_FAILURE);
   }
   
   /* Rangement dans ce fichier de 10 nombres aleatoires a 1 chiffre */
   (void)puts("Rangement dans ce fichier de 10 nombres aleatoires :");
   for ( i = 0; i < 10; i++)
   {
      nombre = rand() % 10;
      (void)printf("%d\n", nombre);
      (void)fprintf(h_ftemp, "%d\n", nombre);
   }
   
   /* On peut fermer le fichier et l'exploiter ulterieurement */
   (void)puts("Fermeture du fichier temporaire.");
   if ( fclose(h_ftemp) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la fermeture du fichier par remove()");
      exit(EXIT_FAILURE);
   }
   
   /* Reouverture du fichier temporaire */
   (void)puts("Reouverture du fichier temporaire.");
   if ( (h_ftemp = fopen(nomfic, "r")) == NULL )
   {
      perror("Erreur sur fopen()");
      exit(EXIT_FAILURE);
   }

   /* Relecture des nombres aleatoires ranges dans le fichier */
   (void)puts("Relecture des nombres aleatoires ranges dans le fichier :");
   while ( fgets(buffer, TAILLE_BUF, h_ftemp) != NULL )
   {
      (void)fputs(buffer, stdout); /* Evite l'ajout d'un \n supplémentaire */
   }
   
   /* Fermeture et destruction du fichier temporaire */
   if ( fclose(h_ftemp) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la fermeture du fichier par fclose()");
      exit(EXIT_FAILURE);
   }
   (void)puts("Destruction du fichier temporaire");
   if ( remove(nomfic) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la destruction du fichier par remove()");
      exit(EXIT_FAILURE);
   }

   (void)puts("Fin du programme tmpnam.");
   return EXIT_SUCCESS;
}

Autres fonctions modifier

Les systèmes genre UNIX offrent d'autres fonctions pour respectivement la création : de noms de fichiers, de fichiers et de répertoires temporaires : mktemp, mkstemp, mkdtemp.

Problèmes de sécurité possibles modifier

Si les données à stocker dans le fichier temporaire ne doivent pas être connues hors du programme, certaines fonctions peuvent poser des problèmes de sécurité :

  • Les fichiers peuvent être créés avec des droits d'accès trop ouverts.
  • Des programmes malveillants peuvent scruter certains répertoires pour connaître les fichiers créés et piller les données contenues.
  • Certains artifices malveillants peuvent agir dans la phase située entre la création du nom de fichier et son ouverture.

Gestion de fichiers modifier

Généralités modifier

stdio.h fournit les prototypes de deux fonctions remove et rename qui permettent de détruire ou de renommer un fichier.

D'autres fonctions de manipulation du système de fichier sont standardisée par la norme POSIX 1003.1.

  Pour la description de ces fonctions système, voir le wikilivre Programmation POSIX.

remove : suppression de fichier modifier

La fonction remove supprime un fichier ou un répertoire vide de la liste des entrées d'un répertoire. Elle retourne 0 en cas de succès.

/*
Nom : remove.c
Auteur : Thierry46
Role : Supprime le fichier dont le nom est passé en paramètre.
Code retour : 0 (EXIT_SUCCESS), EXIT_FAILURE si probleme
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -pedantic -o remove.exe remove.c
Pour exécuter, tapez : ./remove.exe nom_fichier
Version : 1.0 du 10/4/2008
Licence : GNU GPL
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
int main(int argc, char *argv[])
{
   /* test rapide des parametres : un seul obligatoire */
   assert(argc == 2);

   /* Tente de supprimer le fichier dont le nom est passe en parametre */
   if (remove(argv[1]) == 0)
   {
      (void)printf("Le fichier %s est detruit\n", argv[1]);
   }
   else
   {
      perror(argv[1]);
      exit(EXIT_FAILURE);
   }

   return EXIT_SUCCESS;
}

rename : renommer un fichier ou un répertoire modifier

La fonction rename permet de changer le nom d'un fichier ou d'un répertoire. Elle accepte deux chaînes de caractères en paramètre :

  • la première est le nom du fichier à renommer,
  • le deuxième est son nouveau nom.

Elle retourne 0 en cas de succès ou un code d'erreur en cas de problème.

/*
 Nom : rename.c
 Auteur : Thierry46
 Role : Renomme un fichier ou un repertoire.
 Parametres :
 - 1er : ancien nom
 - 2eme : nouveau nom
 Code retour : 0 (EXIT_SUCCESS), EXIT_FAILURE si probleme
 Pour produire un exécutable avec le compilateur libre GCC :
 gcc -Wall -pedantic -o test_rename.exe rename.c
 Pour exécuter, tapez : ./test_rename.exe source dest
 Version : 1.0 du 11/4/2008
 Licence : GNU GPL
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(int argc, char *argv[])
{
   /* test rapide des parametres : deux obligatoires */
   assert(argc == 3);
   
   /* Tente de renommer le fichier */
   if (rename(argv[1], argv[2]) == 0)
   {
      (void)printf("Le fichier %s est renomme en %s\n",
                   argv[1], argv[2]);
   }
   else
   {
      perror("rename");
      (void)fprintf(stderr,
                   "Impossible de renommer le fichier %s en %s\n",
                   argv[1], argv[2]);
      exit(EXIT_FAILURE);
   }
   
   return EXIT_SUCCESS;
}