Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Vous êtes un professionnel et vous avez besoin d'une formation ? Programmation avec
Le langage C
Voir le programme détaillé

Entrées / sorties et fichiers

Gestion des erreurs en C Débogage et diagnostics


Accès rapide :
Les trois flux standards
Ouvrir et fermer un fichier
Lire un fichier texte
Ecrire dans un fichier
Texte ou binaire
Ecrire des données binaires
Relire des données binaires
Enregistrer des structures
Accès direct dans un fichier binaire

La librairie <stdio.h> fournit une abstraction importante : le flux. Un flux peut représenter un fichier, mais aussi l'entrée standard, la sortie standard ou la sortie d'erreur de votre programme. En C, on manipule ces flux au travers du type FILE *.

Les trois flux standards

Au démarrage, un programme possède trois flux déjà ouverts : stdin pour les entrées, stdout pour les sorties normales et stderr pour les messages d'erreur. Cette séparation permet, par exemple, de rediriger les résultats dans un fichier tout en conservant les erreurs à l'écran.

Ouvrir et fermer un fichier

Pour lire ou écrire dans un fichier, il faut d'abord l'ouvrir avec fopen. Cette fonction peut échouer : chemin incorrect, droits insuffisants, fichier absent, etc. Le pointeur retourné doit donc être testé avant toute utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
#include <stdio.h>
#include <stdlib.h>

int main() {

    FILE * inputFile = fopen( "data.txt", "r" );
    if ( inputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    fclose( inputFile );
    return EXIT_SUCCESS;
}
Ouverture et fermeture d'un fichier

Lire un fichier texte

Pour une première approche, fgets reste une fonction très pratique : elle lit au plus un certain nombre de caractères et protège donc votre buffer contre les dépassements. Sur un système POSIX, la fonction getline permet d'aller plus loin en ajustant automatiquement le buffer.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
#include <stdio.h>
#include <stdlib.h>

int main() {

    FILE * inputFile = fopen( "data.txt", "r" );
    if ( inputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    char line[128];
    while ( fgets( line, sizeof line, inputFile ) != NULL ) {
        printf( "%s", line );
    }

    if ( ferror( inputFile ) ) {
        perror( "fgets" );
    }

    fclose( inputFile );
    return EXIT_SUCCESS;
}
Lecture ligne par ligne

Ecrire dans un fichier

Pour écrire du texte formaté dans un fichier, vous pouvez utiliser fprintf. Le principe est le même que pour printf, à ceci près que le premier paramètre indique le flux de destination.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
#include <stdio.h>
#include <stdlib.h>

int main() {

    FILE * outputFile = fopen( "result.txt", "w" );
    if ( outputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    fprintf( outputFile, "pi vaut environ %.2f\n", 3.14159 );
    fclose( outputFile );

    return EXIT_SUCCESS;
}
Ecriture formatée

Texte ou binaire

Les exemples précédents manipulent des fichiers texte : les données sont écrites sous une forme lisible par un humain, et certaines conversions peuvent être effectuées par la librairie C. En mode binaire, les octets sont lus et écrits tels quels. C'est souvent plus compact et plus rapide, mais ce n'est pas pour autant un format d'échange universel : la taille des types, l'alignement des structures ou l'ordre des octets peuvent varier d'une plateforme à l'autre.

Pour un fichier binaire, on ajoute la lettre b au mode d'ouverture de fopen. Sous Unix, la différence avec le mode texte est généralement faible. Sous Windows, elle est importante, car le mode texte peut transformer certaines fins de ligne.

Mode Utilisation
"rb" ouvrir un fichier binaire existant en lecture ;
"wb" créer ou remplacer un fichier binaire en écriture ;
"ab" ajouter des données binaires à la fin du fichier ;
"r+b" ouvrir un fichier binaire existant en lecture et écriture ;
"w+b" créer ou remplacer un fichier binaire en lecture et écriture.

Ecrire des données binaires

La fonction fwrite écrit un bloc mémoire dans un flux. Elle reçoit l'adresse du premier octet à écrire, la taille d'un élément et le nombre d'éléments à transférer. Sa valeur de retour indique le nombre d'éléments réellement écrits : il faut donc la tester.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
#include <stdio.h>
#include <stdlib.h>

int main() {

    int values[] = { 10, 20, 30, 40, 50 };
    size_t count = sizeof values / sizeof values[0];

    FILE * outputFile = fopen( "values.bin", "wb" );
    if ( outputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    size_t written = fwrite( values, sizeof values[0], count, outputFile );
    if ( written != count ) {
        perror( "fwrite" );
        fclose( outputFile );
        return EXIT_FAILURE;
    }

    fclose( outputFile );
    return EXIT_SUCCESS;
}
Ecriture d'un tableau d'entiers en binaire

Dans cet exemple, le fichier ne contient pas les caractères 1, 0, puis un séparateur : il contient la représentation binaire des entiers. Un éditeur de texte ne permettra donc pas de lire correctement le fichier produit.

Relire des données binaires

La lecture utilise fread, sur le même principe. Une lecture courte peut signaler une fin de fichier normale ou une erreur d'entrée / sortie. Pour distinguer les deux cas, on peut tester ferror après l'appel.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
#include <stdio.h>
#include <stdlib.h>

int main() {

    int values[5];
    size_t count = sizeof values / sizeof values[0];

    FILE * inputFile = fopen( "values.bin", "rb" );
    if ( inputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    size_t read = fread( values, sizeof values[0], count, inputFile );
    if ( read != count ) {
        if ( ferror( inputFile ) ) {
            perror( "fread" );
        } else {
            fprintf( stderr, "Le fichier ne contient pas assez de valeurs.\n" );
        }
        fclose( inputFile );
        return EXIT_FAILURE;
    }

    for ( size_t index = 0; index < count; ++index ) {
        printf( "values[%zu] == %d\n", index, values[index] );
    }

    fclose( inputFile );
    return EXIT_SUCCESS;
}
Lecture d'un tableau d'entiers en binaire

Enregistrer des structures

On peut aussi écrire des structures complètes. C'est pratique pour un fichier interne à votre programme, mais il faut rester prudent : une structure C peut contenir des octets d'alignement, et sa représentation exacte n'est pas forcément identique sur deux compilateurs ou deux machines différentes.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    double balance;
} Account;

int main() {

    Account accounts[] = {
        { 1, 1200.50 },
        { 2, 850.00 },
        { 3, 42.75 }
    };
    size_t count = sizeof accounts / sizeof accounts[0];

    FILE * outputFile = fopen( "accounts.bin", "wb" );
    if ( outputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    if ( fwrite( accounts, sizeof accounts[0], count, outputFile ) != count ) {
        perror( "fwrite" );
        fclose( outputFile );
        return EXIT_FAILURE;
    }

    fclose( outputFile );
    return EXIT_SUCCESS;
}
Sauvegarde de quelques enregistrements

Accès direct dans un fichier binaire

Un fichier binaire contenant des enregistrements de taille fixe permet d'accéder directement à une donnée. Pour cela, on se positionne avec fseek. La position courante peut être récupérée avec ftell, et rewind permet de revenir au début du fichier.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    double balance;
} Account;

int main() {

    FILE * inputFile = fopen( "accounts.bin", "rb" );
    if ( inputFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    long recordIndex = 2;        /* Troisième enregistrement : index 2. */
    long offset = recordIndex * (long) sizeof( Account );

    if ( fseek( inputFile, offset, SEEK_SET ) != 0 ) {
        perror( "fseek" );
        fclose( inputFile );
        return EXIT_FAILURE;
    }

    Account account;
    if ( fread( &account, sizeof account, 1, inputFile ) != 1 ) {
        if ( ferror( inputFile ) ) {
            perror( "fread" );
        } else {
            fprintf( stderr, "Enregistrement absent.\n" );
        }
        fclose( inputFile );
        return EXIT_FAILURE;
    }

    printf( "Compte %d : %.2f\n", account.id, account.balance );

    fclose( inputFile );
    return EXIT_SUCCESS;
}
Lecture directe du troisième enregistrement

Le même principe permet de modifier un enregistrement en place. Il faut ouvrir le fichier en lecture / écriture avec "r+b", lire l'enregistrement, revenir au même emplacement, puis réécrire la structure modifiée.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    double balance;
} Account;

int main() {

    FILE * dataFile = fopen( "accounts.bin", "r+b" );
    if ( dataFile == NULL ) {
        perror( "fopen" );
        return EXIT_FAILURE;
    }

    long recordIndex = 1;
    long offset = recordIndex * (long) sizeof( Account );

    if ( fseek( dataFile, offset, SEEK_SET ) != 0 ) {
        perror( "fseek" );
        fclose( dataFile );
        return EXIT_FAILURE;
    }

    Account account;
    if ( fread( &account, sizeof account, 1, dataFile ) != 1 ) {
        if ( ferror( dataFile ) ) {
            perror( "fread" );
        } else {
            fprintf( stderr, "Enregistrement absent.\n" );
        }
        fclose( dataFile );
        return EXIT_FAILURE;
    }

    account.balance += 100.0;

    if ( fseek( dataFile, offset, SEEK_SET ) != 0 ) {
        perror( "fseek" );
        fclose( dataFile );
        return EXIT_FAILURE;
    }

    if ( fwrite( &account, sizeof account, 1, dataFile ) != 1 ) {
        perror( "fwrite" );
        fclose( dataFile );
        return EXIT_FAILURE;
    }

    fclose( dataFile );
    return EXIT_SUCCESS;
}
Modification d'un enregistrement en place

Dès que le fichier doit être échangé avec d'autres programmes, il est préférable de définir un format explicite : écrire les champs un par un, documenter les tailles, fixer l'ordre des octets, ou utiliser un format existant. L'écriture directe d'une structure est surtout intéressante pour des fichiers temporaires ou très liés à une version précise de votre programme.

Gestion des erreurs en C Débogage et diagnostics




Vous êtes un professionnel et vous avez besoin d'une formation ? Programmation avec
Le langage C
Voir le programme détaillé