Fichiers Binaires en C : Lecture, Ecriture et Serialisation de Donnees

Maitrisez les fichiers binaires en C : fread, fwrite, endianness, serialisation de structures et portabilite des donnees binaires.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 12 min read
Fichiers Binaires en C : Lecture, Ecriture et Serialisation de Donnees
Table of Contents

Fichiers Binaires en C : Guide Complet

Les fichiers binaires constituent un pilier fondamental de la programmation systeme en C. Contrairement aux fichiers texte qui stockent des donnees sous forme de caracteres lisibles par l’humain, les fichiers binaires preservent la representation exacte des donnees en memoire. Cette caracteristique en fait le choix privilegie pour le stockage efficace de donnees structurees, les formats de fichiers proprietaires, et les communications inter-processus.

Fichiers Texte vs Fichiers Binaires

Avant de plonger dans les details techniques, il est essentiel de comprendre la difference fondamentale entre ces deux types de fichiers.

Fichiers Texte

Les fichiers texte stockent les donnees sous forme de sequences de caracteres ASCII ou Unicode. Lorsque vous ecrivez le nombre 12345 dans un fichier texte, il occupe 5 octets (un par chiffre). Les retours a la ligne peuvent etre convertis selon le systeme d’exploitation (\n sur Unix, \r\n sur Windows).

// Ecriture d'un entier en mode texte
FILE *fp = fopen("nombre.txt", "w");
fprintf(fp, "%d", 12345);  // Ecrit "12345" (5 caracteres)
fclose(fp);

Fichiers Binaires

Les fichiers binaires stockent les donnees exactement comme elles apparaissent en memoire. Un entier 32 bits occupe toujours 4 octets, peu importe sa valeur. Aucune conversion de caracteres n’est effectuee.

// Ecriture d'un entier en mode binaire
FILE *fp = fopen("nombre.bin", "wb");
int valeur = 12345;
fwrite(&valeur, sizeof(int), 1, fp);  // Ecrit 4 octets
fclose(fp);

Tableau Comparatif

CaracteristiqueFichier TexteFichier Binaire
LisibiliteHumainement lisibleNecessite un programme
TailleVariable selon les donneesPrevisible et compacte
PortabiliteGeneralement portableDepend de l’architecture
ConversionCaracteres de fin de ligneAucune conversion
PerformancePlus lente (parsing)Rapide (lecture directe)
Cas d’usageLogs, config, CSVImages, audio, bases de donnees

Ouverture de Fichiers Binaires

L’ouverture d’un fichier binaire en C se fait avec la fonction fopen() en utilisant le suffixe b dans le mode d’ouverture.

Modes d’Ouverture

ModeDescriptionComportement
"rb"Lecture binaireErreur si fichier inexistant
"wb"Ecriture binaireCree ou ecrase le fichier
"ab"Ajout binaireCree si inexistant, ajoute a la fin
"r+b" ou "rb+"Lecture/EcritureErreur si fichier inexistant
"w+b" ou "wb+"Lecture/EcritureCree ou ecrase le fichier
"a+b" ou "ab+"Lecture/AjoutCree si inexistant

Ouverture Securisee avec Gestion d’Erreurs

Une ouverture de fichier peut echouer pour de nombreuses raisons : fichier inexistant, permissions insuffisantes, disque plein, etc. Il est crucial de toujours verifier le retour de fopen().

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

FILE *ouvrir_fichier_binaire(const char *chemin, const char *mode) {
    FILE *fp = fopen(chemin, mode);

    if (fp == NULL) {
        fprintf(stderr, "Erreur lors de l'ouverture de '%s': %s\n",
                chemin, strerror(errno));
        return NULL;
    }

    return fp;
}

int main(void) {
    FILE *fp = ouvrir_fichier_binaire("donnees.bin", "rb");

    if (fp == NULL) {
        return EXIT_FAILURE;
    }

    // Traitement du fichier...

    fclose(fp);
    return EXIT_SUCCESS;
}

Verification du Type de Fichier

Avant de lire un fichier binaire, il peut etre utile de verifier sa signature (magic number) pour s’assurer qu’il s’agit du bon format.

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

#define MAGIC_NUMBER 0x44455653  // "DEVS" en ASCII

bool verifier_signature(FILE *fp) {
    uint32_t signature;

    // Sauvegarder la position actuelle
    long position_initiale = ftell(fp);

    // Lire les 4 premiers octets
    if (fread(&signature, sizeof(uint32_t), 1, fp) != 1) {
        return false;
    }

    // Restaurer la position
    fseek(fp, position_initiale, SEEK_SET);

    return signature == MAGIC_NUMBER;
}

Lecture avec fread()

La fonction fread() est l’outil principal pour lire des donnees binaires en C. Elle permet de lire un nombre specifie d’elements d’une taille donnee directement en memoire.

Prototype et Parametres

size_t fread(void *ptr, size_t taille, size_t nombre, FILE *flux);
ParametreDescription
ptrPointeur vers le buffer de destination
tailleTaille en octets de chaque element
nombreNombre d’elements a lire
fluxPointeur vers le fichier ouvert
RetourNombre d’elements effectivement lus

Lecture d’un Entier Simple

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

int main(void) {
    FILE *fp = fopen("entier.bin", "rb");
    if (fp == NULL) {
        perror("Erreur ouverture");
        return EXIT_FAILURE;
    }

    int valeur;
    size_t lus = fread(&valeur, sizeof(int), 1, fp);

    if (lus != 1) {
        if (feof(fp)) {
            fprintf(stderr, "Fin de fichier atteinte\n");
        } else if (ferror(fp)) {
            fprintf(stderr, "Erreur de lecture\n");
        }
        fclose(fp);
        return EXIT_FAILURE;
    }

    printf("Valeur lue: %d\n", valeur);

    fclose(fp);
    return EXIT_SUCCESS;
}

Lecture d’un Tableau

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

#define TAILLE_BUFFER 100

int main(void) {
    FILE *fp = fopen("tableau.bin", "rb");
    if (fp == NULL) {
        perror("Erreur ouverture");
        return EXIT_FAILURE;
    }

    double tableau[TAILLE_BUFFER];
    size_t elements_lus = fread(tableau, sizeof(double), TAILLE_BUFFER, fp);

    printf("Elements lus: %zu\n", elements_lus);

    for (size_t i = 0; i < elements_lus; i++) {
        printf("tableau[%zu] = %f\n", i, tableau[i]);
    }

    fclose(fp);
    return EXIT_SUCCESS;
}

Lecture d’une Structure

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

typedef struct {
    int id;
    char nom[50];
    double solde;
} Compte;

int lire_comptes(const char *fichier, Compte *comptes, size_t max_comptes) {
    FILE *fp = fopen(fichier, "rb");
    if (fp == NULL) {
        return -1;
    }

    size_t lus = fread(comptes, sizeof(Compte), max_comptes, fp);

    fclose(fp);
    return (int)lus;
}

int main(void) {
    Compte comptes[10];
    int nombre = lire_comptes("comptes.bin", comptes, 10);

    if (nombre < 0) {
        perror("Erreur de lecture");
        return EXIT_FAILURE;
    }

    printf("Nombre de comptes lus: %d\n", nombre);

    for (int i = 0; i < nombre; i++) {
        printf("ID: %d, Nom: %s, Solde: %.2f\n",
               comptes[i].id, comptes[i].nom, comptes[i].solde);
    }

    return EXIT_SUCCESS;
}

Verification du Nombre d’Elements Lus

Il est essentiel de toujours verifier la valeur de retour de fread(). Une lecture partielle peut indiquer une fin de fichier ou une erreur.

size_t lecture_securisee(void *buffer, size_t taille, size_t nombre, FILE *fp) {
    size_t lus = fread(buffer, taille, nombre, fp);

    if (lus != nombre) {
        if (feof(fp)) {
            // Fin de fichier - peut etre normal
            fprintf(stderr, "Fin de fichier: %zu/%zu elements lus\n", lus, nombre);
        }
        if (ferror(fp)) {
            // Erreur de lecture - probleme serieux
            fprintf(stderr, "Erreur de lecture: %s\n", strerror(errno));
            clearerr(fp);  // Reinitialiser les indicateurs d'erreur
        }
    }

    return lus;
}

Ecriture avec fwrite()

La fonction fwrite() est le pendant de fread() pour l’ecriture. Elle ecrit des donnees binaires depuis la memoire vers un fichier.

Prototype et Parametres

size_t fwrite(const void *ptr, size_t taille, size_t nombre, FILE *flux);
ParametreDescription
ptrPointeur vers les donnees a ecrire
tailleTaille en octets de chaque element
nombreNombre d’elements a ecrire
fluxPointeur vers le fichier ouvert
RetourNombre d’elements effectivement ecrits

Ecriture d’un Entier Simple

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

int main(void) {
    FILE *fp = fopen("entier.bin", "wb");
    if (fp == NULL) {
        perror("Erreur ouverture");
        return EXIT_FAILURE;
    }

    int valeur = 42;
    size_t ecrits = fwrite(&valeur, sizeof(int), 1, fp);

    if (ecrits != 1) {
        fprintf(stderr, "Erreur d'ecriture\n");
        fclose(fp);
        return EXIT_FAILURE;
    }

    printf("Entier ecrit avec succes\n");

    fclose(fp);
    return EXIT_SUCCESS;
}

Ecriture d’un Tableau

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

int main(void) {
    double mesures[] = {1.5, 2.7, 3.14159, 4.0, 5.5};
    size_t taille = sizeof(mesures) / sizeof(mesures[0]);

    FILE *fp = fopen("mesures.bin", "wb");
    if (fp == NULL) {
        perror("Erreur ouverture");
        return EXIT_FAILURE;
    }

    // Ecrire d'abord le nombre d'elements
    fwrite(&taille, sizeof(size_t), 1, fp);

    // Puis ecrire le tableau
    size_t ecrits = fwrite(mesures, sizeof(double), taille, fp);

    if (ecrits != taille) {
        fprintf(stderr, "Ecriture partielle: %zu/%zu\n", ecrits, taille);
    }

    fclose(fp);
    return EXIT_SUCCESS;
}

Ecriture d’une Structure

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

typedef struct {
    int id;
    char nom[50];
    double solde;
} Compte;

int sauvegarder_compte(const char *fichier, const Compte *compte) {
    FILE *fp = fopen(fichier, "ab");  // Mode ajout
    if (fp == NULL) {
        return -1;
    }

    size_t ecrits = fwrite(compte, sizeof(Compte), 1, fp);

    fclose(fp);
    return (ecrits == 1) ? 0 : -1;
}

int main(void) {
    Compte nouveau = {
        .id = 1001,
        .nom = "Jean Dupont",
        .solde = 1500.50
    };

    if (sauvegarder_compte("comptes.bin", &nouveau) == 0) {
        printf("Compte sauvegarde avec succes\n");
    } else {
        fprintf(stderr, "Erreur de sauvegarde\n");
    }

    return EXIT_SUCCESS;
}

Flushing des Donnees

Les donnees ecrites avec fwrite() peuvent etre bufferisees. Pour s’assurer qu’elles sont physiquement ecrites sur le disque, utilisez fflush().

#include <stdio.h>

void ecriture_critique(FILE *fp, const void *donnees, size_t taille) {
    fwrite(donnees, taille, 1, fp);

    // Forcer l'ecriture sur le disque
    fflush(fp);

    // Pour une garantie maximale (depend du systeme)
    #ifdef _POSIX_VERSION
    fsync(fileno(fp));  // POSIX seulement
    #endif
}

Endianness : Big-Endian vs Little-Endian

L’endianness (ou boutisme) definit l’ordre dans lequel les octets d’un nombre multi-octets sont stockes en memoire. C’est un concept crucial pour la portabilite des fichiers binaires.

Comprendre l’Endianness

Considerons le nombre hexadecimal 0x12345678 sur 32 bits (4 octets).

TypeAdresse 0Adresse 1Adresse 2Adresse 3
Big-Endian0x120x340x560x78
Little-Endian0x780x560x340x12
  • Big-Endian : L’octet de poids fort (MSB) est stocke a l’adresse la plus basse. Utilise par les processeurs PowerPC, SPARC, et les protocoles reseau (network byte order).
  • Little-Endian : L’octet de poids faible (LSB) est stocke a l’adresse la plus basse. Utilise par les processeurs x86, x86-64, ARM (en general).

Detection de l’Endianness

#include <stdio.h>
#include <stdbool.h>

bool est_little_endian(void) {
    unsigned int x = 1;
    char *c = (char*)&x;
    return (bool)*c;
}

bool est_big_endian(void) {
    return !est_little_endian();
}

int main(void) {
    if (est_little_endian()) {
        printf("Cette machine est Little-Endian\n");
    } else {
        printf("Cette machine est Big-Endian\n");
    }
    return 0;
}

Fonctions de Conversion Standard

Les fonctions de la bibliotheque <arpa/inet.h> (POSIX) permettent de convertir entre l’ordre de la machine (host) et l’ordre reseau (big-endian).

#include <arpa/inet.h>  // POSIX
#include <stdint.h>

// Conversion host -> network (Big-Endian)
uint16_t htons(uint16_t hostshort);   // 16 bits
uint32_t htonl(uint32_t hostlong);    // 32 bits

// Conversion network -> host
uint16_t ntohs(uint16_t netshort);    // 16 bits
uint32_t ntohl(uint32_t netlong);     // 32 bits

Implementation Portable des Conversions

Si vous n’avez pas acces aux fonctions POSIX, voici des implementations portables.

#include <stdint.h>

// Conversion vers Big-Endian (portable)
uint16_t to_big_endian_16(uint16_t valeur) {
    return ((valeur & 0xFF00) >> 8) | ((valeur & 0x00FF) << 8);
}

uint32_t to_big_endian_32(uint32_t valeur) {
    return ((valeur & 0xFF000000) >> 24) |
           ((valeur & 0x00FF0000) >> 8)  |
           ((valeur & 0x0000FF00) << 8)  |
           ((valeur & 0x000000FF) << 24);
}

uint64_t to_big_endian_64(uint64_t valeur) {
    return ((valeur & 0xFF00000000000000ULL) >> 56) |
           ((valeur & 0x00FF000000000000ULL) >> 40) |
           ((valeur & 0x0000FF0000000000ULL) >> 24) |
           ((valeur & 0x000000FF00000000ULL) >> 8)  |
           ((valeur & 0x00000000FF000000ULL) << 8)  |
           ((valeur & 0x0000000000FF0000ULL) << 24) |
           ((valeur & 0x000000000000FF00ULL) << 40) |
           ((valeur & 0x00000000000000FFULL) << 56);
}

Ecriture Portable d’Entiers

Pour creer des fichiers binaires portables entre differentes architectures, ecrivez les entiers octet par octet dans un ordre defini.

#include <stdio.h>
#include <stdint.h>

// Ecrire un entier 16 bits en Little-Endian
int fwrite_le16(FILE *fp, uint16_t valeur) {
    unsigned char octets[2];
    octets[0] = valeur & 0xFF;
    octets[1] = (valeur >> 8) & 0xFF;
    return fwrite(octets, 1, 2, fp) == 2 ? 0 : -1;
}

// Ecrire un entier 32 bits en Little-Endian
int fwrite_le32(FILE *fp, uint32_t valeur) {
    unsigned char octets[4];
    octets[0] = valeur & 0xFF;
    octets[1] = (valeur >> 8) & 0xFF;
    octets[2] = (valeur >> 16) & 0xFF;
    octets[3] = (valeur >> 24) & 0xFF;
    return fwrite(octets, 1, 4, fp) == 4 ? 0 : -1;
}

// Lire un entier 32 bits en Little-Endian
int fread_le32(FILE *fp, uint32_t *valeur) {
    unsigned char octets[4];
    if (fread(octets, 1, 4, fp) != 4) {
        return -1;
    }
    *valeur = (uint32_t)octets[0] |
              ((uint32_t)octets[1] << 8) |
              ((uint32_t)octets[2] << 16) |
              ((uint32_t)octets[3] << 24);
    return 0;
}

Serialisation de Structures

La serialisation consiste a convertir une structure de donnees en une sequence d’octets pour le stockage ou la transmission. En C, ce processus est complique par l’alignement memoire et le padding.

Le Probleme du Padding

Le compilateur peut inserer des octets de padding entre les membres d’une structure pour respecter les contraintes d’alignement du processeur.

#include <stdio.h>
#include <stddef.h>

typedef struct {
    char a;      // 1 octet
    // 3 octets de padding (alignement int)
    int b;       // 4 octets
    char c;      // 1 octet
    // 3 octets de padding (alignement fin de structure)
} StructurePaddee;

int main(void) {
    printf("Taille attendue (sans padding): %zu\n",
           sizeof(char) + sizeof(int) + sizeof(char));  // 6
    printf("Taille reelle (avec padding): %zu\n",
           sizeof(StructurePaddee));  // Probablement 12

    printf("Offset de a: %zu\n", offsetof(StructurePaddee, a));  // 0
    printf("Offset de b: %zu\n", offsetof(StructurePaddee, b));  // 4
    printf("Offset de c: %zu\n", offsetof(StructurePaddee, c));  // 8

    return 0;
}

Utilisation de #pragma pack

La directive #pragma pack permet de controler l’alignement et d’eliminer le padding. Attention : cela peut impacter les performances.

#include <stdio.h>

// Desactiver le padding
#pragma pack(push, 1)

typedef struct {
    char a;      // 1 octet
    int b;       // 4 octets
    char c;      // 1 octet
} StructureCompacte;  // Total: 6 octets

#pragma pack(pop)  // Restaurer l'alignement par defaut

int main(void) {
    printf("Taille compacte: %zu\n", sizeof(StructureCompacte));  // 6
    return 0;
}

Serialisation Manuelle (Recommandee)

La methode la plus portable consiste a serialiser chaque membre individuellement.

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

typedef struct {
    uint32_t id;
    char nom[32];
    float prix;
    uint16_t quantite;
} Produit;

// Serialiser un produit vers un fichier
int serialiser_produit(FILE *fp, const Produit *p) {
    // Ecrire l'ID en Little-Endian
    if (fwrite_le32(fp, p->id) != 0) return -1;

    // Ecrire le nom (taille fixe)
    if (fwrite(p->nom, 1, 32, fp) != 32) return -1;

    // Ecrire le prix (representation IEEE 754)
    if (fwrite(&p->prix, sizeof(float), 1, fp) != 1) return -1;

    // Ecrire la quantite en Little-Endian
    if (fwrite_le16(fp, p->quantite) != 0) return -1;

    return 0;
}

// Deserialiser un produit depuis un fichier
int deserialiser_produit(FILE *fp, Produit *p) {
    // Lire l'ID
    if (fread_le32(fp, &p->id) != 0) return -1;

    // Lire le nom
    if (fread(p->nom, 1, 32, fp) != 32) return -1;

    // Lire le prix
    if (fread(&p->prix, sizeof(float), 1, fp) != 1) return -1;

    // Lire la quantite
    uint16_t quantite_temp;
    if (fread_le16(fp, &quantite_temp) != 0) return -1;
    p->quantite = quantite_temp;

    return 0;
}

Serialisation avec Entete

Pour creer un format de fichier robuste, incluez un entete avec des metadonnees.

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#define MAGIC_NUMBER 0x50524F44  // "PROD"
#define VERSION_FORMAT 1

typedef struct {
    uint32_t magic;
    uint16_t version;
    uint32_t nombre_produits;
    uint64_t timestamp;
} EnteteFormat;

int ecrire_entete(FILE *fp, uint32_t nombre_produits) {
    EnteteFormat entete = {
        .magic = MAGIC_NUMBER,
        .version = VERSION_FORMAT,
        .nombre_produits = nombre_produits,
        .timestamp = (uint64_t)time(NULL)
    };

    // Serialisation manuelle de l'entete
    fwrite_le32(fp, entete.magic);
    fwrite_le16(fp, entete.version);
    fwrite_le32(fp, entete.nombre_produits);
    fwrite_le64(fp, entete.timestamp);

    return 0;
}

int lire_entete(FILE *fp, EnteteFormat *entete) {
    fread_le32(fp, &entete->magic);
    fread_le16(fp, &entete->version);
    fread_le32(fp, &entete->nombre_produits);
    fread_le64(fp, &entete->timestamp);

    // Verifier la signature
    if (entete->magic != MAGIC_NUMBER) {
        fprintf(stderr, "Format de fichier invalide\n");
        return -1;
    }

    // Verifier la version
    if (entete->version > VERSION_FORMAT) {
        fprintf(stderr, "Version de format non supportee\n");
        return -1;
    }

    return 0;
}

Les fonctions fseek() et ftell() permettent de naviguer dans un fichier binaire pour acceder directement a des positions specifiques.

Positionnement avec fseek()

int fseek(FILE *flux, long offset, int origine);
OrigineDescription
SEEK_SETDepuis le debut du fichier
SEEK_CURDepuis la position courante
SEEK_ENDDepuis la fin du fichier

Exemple : Acces Direct a un Enregistrement

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

typedef struct {
    int id;
    char nom[50];
    double solde;
} Compte;

Compte* lire_compte_par_index(FILE *fp, size_t index) {
    static Compte compte;

    // Calculer l'offset
    long offset = index * sizeof(Compte);

    // Se positionner
    if (fseek(fp, offset, SEEK_SET) != 0) {
        return NULL;
    }

    // Lire
    if (fread(&compte, sizeof(Compte), 1, fp) != 1) {
        return NULL;
    }

    return &compte;
}

size_t compter_enregistrements(FILE *fp) {
    // Aller a la fin
    fseek(fp, 0, SEEK_END);

    // Obtenir la position (= taille du fichier)
    long taille = ftell(fp);

    // Revenir au debut
    fseek(fp, 0, SEEK_SET);

    return taille / sizeof(Compte);
}

Bonnes Pratiques

L’ecriture de code robuste pour les fichiers binaires necessite de suivre plusieurs principes fondamentaux.

1. Toujours Verifier les Valeurs de Retour

// Mauvais
fread(&data, sizeof(data), 1, fp);

// Bon
if (fread(&data, sizeof(data), 1, fp) != 1) {
    // Gerer l'erreur
}

2. Definir un Format de Fichier Clair

Documentez votre format de fichier avec precision : l’ordre des champs, leur taille, l’endianness utilise, et la signification de chaque octet.

3. Utiliser des Types de Taille Fixe

// Mauvais - taille variable selon la plateforme
int valeur;
long grand_nombre;

// Bon - taille garantie
#include <stdint.h>
int32_t valeur;
int64_t grand_nombre;

4. Gerer l’Endianness de Maniere Explicite

Choisissez un ordre d’octets pour votre format et convertissez systematiquement lors de la lecture/ecriture.

5. Inclure des Metadonnees

typedef struct {
    uint32_t magic;           // Signature du format
    uint16_t version;         // Version du format
    uint32_t checksum;        // Verification d'integrite
    uint64_t creation_time;   // Horodatage
} EnteteFormatRobuste;

6. Fermer les Fichiers Proprement

FILE *fp = fopen("fichier.bin", "rb");
if (fp == NULL) {
    // Gerer l'erreur
}

// ... operations ...

if (fclose(fp) != 0) {
    // Gerer l'erreur de fermeture
}

7. Utiliser des Buffers Appropries

Pour les fichiers volumineux, utilisez setvbuf() pour optimiser les performances.

FILE *fp = fopen("gros_fichier.bin", "rb");
char buffer[65536];  // 64 Ko
setvbuf(fp, buffer, _IOFBF, sizeof(buffer));

Pieges Courants

1. Oublier le Mode Binaire

// Piege : sans 'b', les fins de ligne peuvent etre converties
FILE *fp = fopen("data.bin", "r");   // FAUX
FILE *fp = fopen("data.bin", "rb");  // CORRECT

2. Assumer la Taille des Types

// Piege : sizeof(int) peut varier
fwrite(&valeur, 4, 1, fp);

// Correct
fwrite(&valeur, sizeof(int), 1, fp);

// Encore mieux : utiliser des types de taille fixe
int32_t valeur32;
fwrite(&valeur32, sizeof(int32_t), 1, fp);

3. Ignorer le Padding des Structures

// Piege : le padding peut varier selon le compilateur
typedef struct {
    char a;
    int b;
} MaStruct;
fwrite(&s, sizeof(MaStruct), 1, fp);  // Non portable

// Correct : serialiser membre par membre
fwrite(&s.a, sizeof(char), 1, fp);
fwrite(&s.b, sizeof(int), 1, fp);

4. Ne Pas Verifier fread/fwrite

// Piege : ignorer le retour peut masquer des erreurs
fread(&data, sizeof(data), 1, fp);

// Correct
if (fread(&data, sizeof(data), 1, fp) != 1) {
    if (feof(fp)) {
        // Fin de fichier
    } else {
        // Erreur de lecture
    }
}

5. Confondre les Parametres de fread/fwrite

// Piege : ordre inverse taille/nombre
fread(buffer, nombre, taille, fp);

// Correct
fread(buffer, taille_element, nombre_elements, fp);

6. Oublier rewind() apres fwrite

Si vous ecrivez puis lisez dans le meme fichier, n’oubliez pas de repositionner le curseur.

FILE *fp = fopen("test.bin", "w+b");
fwrite(&data, sizeof(data), 1, fp);

// Piege : le curseur est a la fin
fread(&data_lue, sizeof(data_lue), 1, fp);  // Ne lit rien

// Correct
rewind(fp);  // ou fseek(fp, 0, SEEK_SET);
fread(&data_lue, sizeof(data_lue), 1, fp);

7. Problemes de Portabilite des Flottants

La representation des flottants (IEEE 754) est generalement portable, mais pas garantie. Pour une portabilite maximale, convertissez en representation textuelle ou en mantisse/exposant entiers.

Conclusion

La maitrise des fichiers binaires en C est une competence essentielle pour tout developpeur systeme. Les points cles a retenir sont :

  1. Comprendre la difference entre fichiers texte et binaires, et choisir le mode approprie
  2. Maitriser fread() et fwrite() avec une verification systematique des valeurs de retour
  3. Gerer l’endianness explicitement pour creer des fichiers portables
  4. Serialiser les structures membre par membre plutot que de compter sur le layout memoire
  5. Suivre les bonnes pratiques : types de taille fixe, entetes avec metadonnees, verification d’erreurs

Les fichiers binaires offrent des performances superieures et une representation compacte des donnees, mais demandent une attention particuliere aux details d’implementation pour garantir la fiabilite et la portabilite de vos programmes.

Prochaines etapes pour approfondir

  • Explorez les fonctions mmap() pour le mapping de fichiers en memoire (POSIX)
  • Etudiez les formats de fichiers standards (PNG, WAV, ELF) pour comprendre les bonnes pratiques de l’industrie
  • Implementez votre propre format de serialisation pour un projet personnel
  • Experimentez avec les bibliotheques de serialisation comme Protocol Buffers ou MessagePack
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles