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
| Caracteristique | Fichier Texte | Fichier Binaire |
|---|---|---|
| Lisibilite | Humainement lisible | Necessite un programme |
| Taille | Variable selon les donnees | Previsible et compacte |
| Portabilite | Generalement portable | Depend de l’architecture |
| Conversion | Caracteres de fin de ligne | Aucune conversion |
| Performance | Plus lente (parsing) | Rapide (lecture directe) |
| Cas d’usage | Logs, config, CSV | Images, 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
| Mode | Description | Comportement |
|---|---|---|
"rb" | Lecture binaire | Erreur si fichier inexistant |
"wb" | Ecriture binaire | Cree ou ecrase le fichier |
"ab" | Ajout binaire | Cree si inexistant, ajoute a la fin |
"r+b" ou "rb+" | Lecture/Ecriture | Erreur si fichier inexistant |
"w+b" ou "wb+" | Lecture/Ecriture | Cree ou ecrase le fichier |
"a+b" ou "ab+" | Lecture/Ajout | Cree 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);
| Parametre | Description |
|---|---|
ptr | Pointeur vers le buffer de destination |
taille | Taille en octets de chaque element |
nombre | Nombre d’elements a lire |
flux | Pointeur vers le fichier ouvert |
| Retour | Nombre 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);
| Parametre | Description |
|---|---|
ptr | Pointeur vers les donnees a ecrire |
taille | Taille en octets de chaque element |
nombre | Nombre d’elements a ecrire |
flux | Pointeur vers le fichier ouvert |
| Retour | Nombre 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).
| Type | Adresse 0 | Adresse 1 | Adresse 2 | Adresse 3 |
|---|---|---|---|---|
| Big-Endian | 0x12 | 0x34 | 0x56 | 0x78 |
| Little-Endian | 0x78 | 0x56 | 0x34 | 0x12 |
- 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;
}
Navigation dans un Fichier Binaire
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);
| Origine | Description |
|---|---|
SEEK_SET | Depuis le debut du fichier |
SEEK_CUR | Depuis la position courante |
SEEK_END | Depuis 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 :
- Comprendre la difference entre fichiers texte et binaires, et choisir le mode approprie
- Maitriser fread() et fwrite() avec une verification systematique des valeurs de retour
- Gerer l’endianness explicitement pour creer des fichiers portables
- Serialiser les structures membre par membre plutot que de compter sur le layout memoire
- 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
In-Article Ad
Dev Mode
Tags
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
Bit-fields et tableaux en C : guide pratique pour optimiser la memoire
Maitrisez les bit-fields et tableaux en C. Apprenez a creer des structures compactes, acceder aux elements et iterer efficacement sur vos donnees.
Header Guards en C : Prevention des Inclusions Multiples et Bonnes Pratiques
Maitrisez les header guards et pragma once en C : prevention des inclusions multiples, conventions de nommage et organisation des fichiers d'en-tete.
Programmation C : Bits, pointeurs et bonnes pratiques
Maîtrisez les pièges courants en C : évaluation des champs de bits, arithmétique des pointeurs, commentaires multi-lignes et comparaison de flottants.