Fichiers binaires et accès direct

@joekakone

Joseph Konka @joekakone

03/03/2026 · 12 min

Découvrez le stockage binaire et l'accès direct en C avec fread, fwrite, fseek et ftell. Apprenez à sérialiser des structures et à manipuler des bases de données sur disque à haute performance.

Fichiers binaires et accès direct#

Les fichiers texte (.txt, .csv) sont parfaitement lisibles par un être humain mais présentent deux inconvénients majeurs en ingénierie logicielle : 1. Perte de performance : convertir des nombres en chaînes de caractères (et inversement avec sprintf ou fscanf) consomme du temps processeur. 2. Accès séquentiel obligatoire : pour lire la \(1\,000^{\text{e}}\) ligne d'un fichier texte, vous êtes contraint de lire et analyser toutes les 999 lignes précédentes.

Les fichiers binaires éliminent ces limitations. Ils permettent de transférer directement des blocs de mémoire vive vers le disque dur sans aucune conversion, et d'accéder instantanément à n'importe quel enregistrement grâce à l'accès direct (random access).


1. Fichier texte vs Fichier binaire#

Considérons l'entier int valeur = 12345678; :

Critère Fichier texte Fichier binaire
Représentation sur disque 8 caractères ASCII : '1', '2', '3', '4', '5', '6', '7', '8' 4 octets bruts (codage binaire 32 bits Little-Endian)
Taille occupée 8 octets 4 octets (fixe quel que soit le nombre)
Vitesse d'écriture/lecture Lente (conversion binaire \(\leftrightarrow\) ASCII) Ultra-rapide (copie brute octet par octet)
Lisibilité Lisible dans un éditeur (Notepad, VS Code) Illisible directement (nécessite un éditeur hexadécimal)

Pour ouvrir un fichier binaire, il suffit d'ajouter la lettre b au mode d'ouverture : "rb", "wb", "ab", "r+b".


2. Écriture binaire par blocs : fwrite()#

La fonction fwrite() copie directement une zone mémoire continue vers le flux binaire :

size_t fwrite(const void *ptr, size_t taille_element, size_t nb_elements, FILE *flux);
  • ptr : pointeur vers les données à enregistrer.
  • taille_element : taille en octets d'une unité (souvent calculée avec sizeof(...)).
  • nb_elements : nombre d'éléments à écrire.
  • flux : pointeur vers le fichier ouvert en mode binaire ("wb" ou "ab").
  • Valeur de retour : le nombre d'éléments effectivement écrits (doit être égal à nb_elements en l'absence d'erreur).
// Exemple : Écriture d'un tableau d'entiers
int nombres[5] = {10, 20, 30, 40, 50};
FILE *f = fopen("donnees.bin", "wb");

if (f != NULL)
{
    size_t ecrits = fwrite(nombres, sizeof(int), 5, f);
    if (ecrits < 5)
    {
        perror("Erreur d'ecriture partielle");
    }
    fclose(f);
}

3. Lecture binaire par blocs : fread()#

Symétriquement, fread() lit des octets bruts depuis le fichier et les injecte directement dans une zone mémoire :

size_t fread(void *ptr, size_t taille_element, size_t nb_elements, FILE *flux);
int copie[5];
FILE *f = fopen("donnees.bin", "rb");

if (f != NULL)
{
    size_t lus = fread(copie, sizeof(int), 5, f);
    printf("%zu entiers lus avec succes.\n", lus);
    fclose(f);
}

4. Navigation et accès direct : fseek, ftell et rewind#

Contrairement à un fichier texte, un fichier binaire stockant des structures de taille fixe (sizeof(MaStructure)) permet de sauter directement à n'importe quel enregistrement sans lire ce qui précède !

flowchart LR
    A[Enregistrement 0 : sizeof] --> B[Enregistrement 1 : sizeof]
    B --> C[Enregistrement 2 : sizeof]
    C --> D[Enregistrement 3 : sizeof]
    Note over C: fseek saut direct vers i * sizeof

La fonction fseek()#

Déplace le curseur interne du fichier vers un décalage (offset) en octets :

int fseek(FILE *flux, long deplacement, int origine);

L'argument origine accepte trois constantes symboliques : - SEEK_SET : déplacement compté depuis le début du fichier. - SEEK_CUR : déplacement compté depuis la position courante. - SEEK_END : déplacement compté depuis la fin du fichier.

La fonction ftell()#

Renvoie la position courante du curseur en nombre d'octets depuis le début du fichier :

long position = ftell(flux);

Déterminer la taille exacte d'un fichier sur disque#

En combinant fseek et ftell, vous pouvez calculer instantanément la taille de n'importe quel fichier :

long ObtenirTailleFichier(const char *chemin)
{
    FILE *f = fopen(chemin, "rb");
    if (f == NULL) return -1;

    // Se déplacer tout à la fin
    fseek(f, 0, SEEK_END);
    long taille = ftell(f);

    // Fermeture
    fclose(f);
    return taille;
}

5. Étude de cas complète : Mini-banque avec mise à jour in-place#

Imaginons une application bancaire où nous voulons mettre à jour le solde d'un compte précis sur disque sans avoir à réécrire l'ensemble de la base de données.

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

typedef struct
{
    int numero_compte;
    char titulaire[40];
    double solde;
} CompteBancaire;

// Initialise le fichier binaire avec quelques comptes
void InitialiserComptes(const char *chemin)
{
    CompteBancaire comptes[3] = {
        {1001, "Alice Martin", 2500.00},
        {1002, "Bob Diakite", 1450.50},
        {1003, "Carla Rossi", 8900.25}
    };

    FILE *f = fopen(chemin, "wb");
    if (!f) return;

    fwrite(comptes, sizeof(CompteBancaire), 3, f);
    fclose(f);
    printf("Base binaire creee avec succes.\n");
}

// Affiche tous les enregistrements du fichier
void AfficherTousLesComptes(const char *chemin)
{
    FILE *f = fopen(chemin, "rb");
    if (!f) return;

    CompteBancaire c;
    int index = 0;

    printf("\n--- ETAT DE LA BASE DE DONNEES ---\n");
    while (fread(&c, sizeof(CompteBancaire), 1, f) == 1)
    {
        printf("Index [%d] | Compte #%d | %-15s | Solde : %10.2f EUR\n",
               index++, c.numero_compte, c.titulaire, c.solde);
    }
    printf("----------------------------------\n\n");

    fclose(f);
}

// Modifie directement le solde d'un compte sans recharger tout le fichier
int CrediterCompte(const char *chemin, int index, double montant)
{
    // Mode "r+b" : lecture et écriture simultanées sans écraser le fichier
    FILE *f = fopen(chemin, "r+b");
    if (f == NULL)
    {
        perror("Impossible d'ouvrir le fichier en r+b");
        return 0;
    }

    // 1. Calcul du décalage mémoire vers l'enregistrement ciblé
    long offset = (long)index * sizeof(CompteBancaire);

    if (fseek(f, offset, SEEK_SET) != 0)
    {
        fclose(f);
        return 0;
    }

    // 2. Lecture de l'enregistrement actuel
    CompteBancaire compte;
    if (fread(&compte, sizeof(CompteBancaire), 1, f) != 1)
    {
        printf("Erreur : Compte a l'index %d inexistant.\n", index);
        fclose(f);
        return 0;
    }

    // 3. Modification en mémoire
    compte.solde += montant;

    // 4. Repositionnement du curseur car la lecture l'a avancé d'un cran !
    fseek(f, offset, SEEK_SET);

    // 5. Réécriture in-place de la structure modifiée
    fwrite(&compte, sizeof(CompteBancaire), 1, f);

    fclose(f);
    printf(">> Compte #%d credite de %.2f EUR (Nouveau solde : %.2f EUR)\n",
           compte.numero_compte, montant, compte.solde);

    return 1;
}

int main(void)
{
    const char *chemin = "banque.dat";

    // Étape 1 : Création de la base
    InitialiserComptes(chemin);
    AfficherTousLesComptes(chemin);

    // Étape 2 : Modification directe par fseek de l'index 1 (Bob Diakite)
    CrediterCompte(chemin, 1, 500.00);

    // Étape 3 : Vérification de la persistance
    AfficherTousLesComptes(chemin);

    return EXIT_SUCCESS;
}

6. Bonnes pratiques de la sérialisation binaire#

[!CAUTION] Ne sérialisez jamais directement des structures contenant des pointeurs ! Si votre structure contient un champ char *adresse, fwrite(&structure, ...) enregistrera la valeur de l'adresse mémoire (ex: 0x7ffee4bc) et non les caractères pointés. Lors d'un redémarrage ultérieur ou sur une autre machine, cette adresse pointera vers une zone mémoire invalide, provoquant un plantage garanti. Pour les données persistantes, utilisez toujours des tableaux de taille fixe (ex: char adresse[100];).


7. Défi d'application#

Développez une fonction RechercherParNumero(const char *chemin, int numero_compte) qui calcule le nombre total d'enregistrements à l'aide de ftell et réalise une recherche dichotomique ou séquentielle dans le fichier binaire pour afficher les détails du compte recherché.

Commentaires

Inscrivez-vous à une formation complète

Voir le catalogue