Tout ce qu'un programme calcule disparaît à sa sortie, sauf s'il l'écrit quelque part. L'interface de fichiers du C vit dans <stdio.h> - le même en-tête que printf - et elle lui ressemble délibérément : un fichier est un flux d'octets, et les fonctions qui travaillent sur l'écran ont des jumelles qui prennent un flux en argument.
Le type de descripteur est FILE *. Vous ne regardez jamais à l'intérieur d'un FILE ; c'est un type opaque (voir typedef) et vous n'en détenez jamais qu'un pointeur.
Les trois étapes
Toute opération sur fichier suit la même forme : ouvrir, utiliser, fermer.
La vérification de NULL n'est pas de la paranoïa facultative. fopen échoue dès que le fichier n'existe pas (en mode lecture), que le répertoire n'est pas accessible en écriture, que le chemin est faux, ou que le processus n'a plus de descripteurs - et chacun de ces cas renvoie NULL. Utiliser un descripteur NULL est une erreur de segmentation.
Envoyer l'erreur vers stderr plutôt que stdout est la convention : ce flux n'est pas tamponné, et il peut être redirigé séparément de la vraie sortie.
Les modes de fopen
Le second argument est une courte chaîne de mode. Se tromper est l'erreur la plus destructrice de tout ce domaine, car "w" vide silencieusement un fichier existant.
| Mode | Lecture | Écriture | Si le fichier existe | S'il n'existe pas |
|---|---|---|---|---|
"r" | oui | non | ouvre au début | échoue, renvoie NULL |
"w" | non | oui | vidé | créé |
"a" | non | oui | écrit uniquement à la fin | créé |
"r+" | oui | oui | ouvre au début | échoue, renvoie NULL |
"w+" | oui | oui | vidé | créé |
"a+" | oui | oui | lit n'importe où, écrit à la fin | créé |
Ajoutez un b à n'importe lequel ("rb", "wb", "ab+") pour le mode binaire - voir la fin de la page.
Deux règles qui évitent de vraies pertes de données :
- Utilisez
"r"quand vous voulez lire."r+"sur un nom de fichier mal tapé échoue sans dégâts ;"w+"crée un fichier vide et vous n'avez perdu que du temps."w"sur le bon nom de fichier alors que vous vouliez"r"détruit les données. - Utilisez
"a"pour les journaux. Chaquefprintfatterrit à la fin où que le flux ait été positionné, ce qui est exactement ce que veut un journal.
Écrire et lire dans un même programme
L'éditeur ci-dessous exécute un aller-retour complet - il crée un fichier, y écrit des enregistrements, le ferme, le rouvre en lecture et affiche ce qu'il trouve.
fprintf et fscanf sont printf et scanf avec un flux comme premier argument ; tout ce qui concerne leurs spécificateurs de format est identique, y compris la largeur %31s qui empêche name de déborder.
La condition de boucle est == 2, le nombre d'éléments que le format demande. Tester contre EOF à la place est un bug classique : une ligne malformée fait renvoyer 0 à fscanf, pas EOF, et la boucle tourne indéfiniment sur une entrée qu'elle ne peut pas consommer.
Lire ligne par ligne avec fgets
fscanf est pratique pour des données rigidement formatées. Pour les fichiers texte - configuration, journaux, CSV, tout ce qu'un humain a écrit - lisez des lignes entières. Cette boucle est la boucle canonique :
Ce qui fait de fgets le bon choix par défaut :
- Il prend la taille du tampon, il ne peut donc pas déborder. Passez
sizeof lineet l'appel reste correct si vous redimensionnez le tableau. - Il renvoie
NULLen fin de fichier ou en cas d'erreur, ce qui est une condition de boucle propre. - Il conserve le saut de ligne quand la ligne tenait dans le tampon. C'est utile - l'absence de
'\n'dans ce que vous avez reçu signifie que la ligne était plus longue que votre tampon et que le reste attend encore.strcspn(line, "\n")trouve l'indice du saut de ligne (ou la longueur de la chaîne s'il n'y en a pas), donc y affecter'\0'le coupe dans les deux cas.
Pour distinguer une vraie fin de fichier d'une erreur, demandez après la boucle :
if (ferror(in)) {
fprintf(stderr, "erreur de lecture\n");
} else if (feof(in)) {
/* fin normale */
}
N'écrivez pas while (!feof(fp)) comme condition de boucle. feof ne devient vrai qu'après qu'une lecture a déjà échoué, donc cette boucle traite le contenu du dernier tampon une fois de trop. Testez plutôt la valeur de retour de la fonction de lecture - comme le font les deux boucles ci-dessus.
Caractère par caractère : fgetc et fputc
Pour un travail au niveau de l'octet - compter des caractères, transformer un fichier, copier - fgetc et fputc traitent un caractère par appel.
Un détail qui compte : c est déclaré int, pas char. fgetc renvoie un int afin de pouvoir renvoyer toutes les valeurs d'octet possibles et la sentinelle distincte EOF (qui vaut -1). Le stocker dans un char rend la comparaison avec EOF soit toujours fausse, soit faussement vraie pour l'octet 0xFF, selon que char est signé sur votre plateforme. C'est l'un des plus vieux pièges du C.
Vérifier les erreurs correctement
Une lecture de production ressemble à ceci :
errno contient un code décrivant le dernier échec et strerror le transforme en phrase ; perror affiche votre message plus cette phrase en un seul appel. Ils demandent respectivement <errno.h> et <string.h>.
fclose peut aussi échouer - il vide les données tamponnées, et l'écriture peut ne pas tenir sur le disque - donc pour tout ce qui compte, vérifiez-le :
if (fclose(fp) != 0) {
fprintf(stderr, "echec du vidage et de la fermeture\n");
}
Le mode binaire en un paragraphe
Le mode texte peut traduire les fins de ligne (sous Windows, \n devient \r\n à l'écriture et l'inverse à la lecture) et traiter certains octets spécialement. Pour des données qui ne sont pas du texte - une image, une structure vidée telle quelle, un blob compressé - ouvrez avec b et utilisez fread/fwrite, qui déplacent des octets bruts :
fwrite(ptr, size, count, fp) écrit count éléments de size octets et renvoie combien il en a écrit ; fread en est le miroir. Sachez qu'un fichier écrit ainsi est lié à la machine qui l'a écrit - le remplissage des structures, la taille des entiers et l'ordre des octets fuient tous dans les octets - c'est donc acceptable pour un cache ou un fichier temporaire et faux pour un format que d'autres programmes doivent lire.
Erreurs courantes
- Ne pas vérifier
fopencontreNULL. Le plantage qui suit est imputé à la lecture, pas à l'ouverture. - Ouvrir avec
"w"alors qu'on voulait"r". Le fichier est vidé avant que vous ne le remarquiez. - Oublier
fclose. La sortie tamponnée est perdue, et chaque fichier non fermé fuit un descripteur. while (!feof(fp)). Traite la dernière ligne deux fois. Testez plutôt l'appel de lecture.char c = fgetc(fp). Casse la comparaison avecEOF. Utilisezint.fscanf("%s", buf)sans largeur. Le même débordement de tampon que scanf au clavier.- Les chemins relatifs.
fopen("data.txt", "r")cherche dans le répertoire de travail, pas à côté de l'exécutable. Si un fichier « disparaît », c'est généralement pour cela.
Questions fréquentes
Comment ouvre-t-on un fichier en C ?
FILE *fp = fopen("data.txt", "r"); ouvre un fichier en lecture et renvoie un descripteur FILE *, ou NULL en cas d'échec. Vérifiez toujours NULL avant d'utiliser le descripteur, et appelez fclose(fp) quand vous avez fini.
Quels sont les modes de fopen en C ?
"r" lecture (le fichier doit exister), "w" écriture (crée, ou vide un fichier existant), "a" ajout (crée, écrit à la fin). Ajouter + rend chacun lecture-écriture : "r+", "w+", "a+". Ajouter b ("rb", "wb") ouvre en mode binaire.
Comment lire un fichier ligne par ligne en C ?
Utilisez fgets dans une boucle while : while (fgets(line, sizeof line, fp) != NULL) { ... }. Il s'arrête à chaque saut de ligne ou quand le tampon est plein, renvoie NULL en fin de fichier, et ne peut pas déborder puisque vous passez la taille du tampon.
Pourquoi mon fichier est-il vide après y avoir écrit en C ?
Le plus souvent, vous avez oublié fclose. La sortie est tamponnée, donc des données peuvent encore être en mémoire quand le programme se termine anormalement. fclose vide le tampon et ferme ; fflush(fp) vide sans fermer. L'autre cause est une seconde ouverture en "w", qui tronque le fichier que vous venez d'écrire.