Lecture de fichier binaire en C++

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je me demande quelle est la meilleure manière en C++ de lire un fichier binaire.

La librairie standard est très vaste et semble contenir des tas de fonctions pour y arriver :
http://www.cplusplus.com/reference/fstream/ifstream/

Concrètement, je dispose d’une fichier binaire de plusieurs centaines de Mo dont je souhaite compter le nombre de caractères nuls :

#include <iostream>
#include <string>
#include <fstream>

using std::ios;
using std::cout;
using std::endl;
using std::string;
using std::ifstream;


int main()
{
    // --- Ouverture du fichier binaire ---
    ifstream f_bin ("binary_file.psq", ios::in | ios::binary);
    
    if (!f_bin.is_open())
    {
        cout << "Erreur lors de l'ouverture du fichier !" << endl;
        return 1;
    }
    
    
    // Obtenir la taille du fichier
    int length;

    f_bin.seekg (0, f_bin.end);
    length = f_bin.tellg();
    f_bin.seekg (0, f_bin.beg);
    
    
    
    // Lire tous les caractères du fichier
    // On souhaite compter le nombre de caractères nuls
    char cara;
    int  cnt = 0;
 
    for (int i = 0; i < length; ++i)
    {
        f_bin.read((char*) &cara, sizeof(char)); 
        
        if (cara == '\0')
            ++cnt;
    }
    
    cout << "Nombre de caractères nuls dans ce fichier binaire : "
         << cnt
         << endl;

    f_bin.close();
    return 0;
}

Je pense que le code fonctionne.
Mais est-ce la meilleure approche pour lire un fichier binaire et parvenir à l’objectif efficacement ?

Je vois deux améliorations importantes possibles:

  • Plutôt que de récupérer la taille du fichier et ensuite faire une boucle for en utilisant cette taille, il serait mieux de directement lire le fichier jusqu’à ce que tu reçoives un end of file (fin de fichier). Tu peux tester ça en utilisant f_bin.eof().
  • Plutôt que de lire le fichier caractère par caractère, il est mieux (pour les performances) de le faire bloc par bloc. La taille optimale varie suivant la situation, mais 1Ko est probablement une valeur sûr (très peu de risque que ce soit trop grand avec une grosse partie des bénéfices). Je serait pas étonné que sur certains systèmes, il soit possible de pousser jusqu’à plusieurs Mo. En lisant bloc par bloc, il est possible que la fin de fichier arrive au milieux d’un bloc, auquel cas, f_bin.gcount() te dira combien de caractères ont été lu avant d’atteindre la fin.

En allant plus loin, je vois qu’il y a une fonction f_bin.ignore() qui permet d’aller au prochain caractère donné (dans ton cas \0). En utilisant ça, ton code se réduit à compter combien de fois tu dois appeler f_bin.ignore() avant d’arriver à la fin du ficher.

Merci pour ces conseils, ça me sera bien utile !
Néanmoins, ma question était plutôt tournée sur l’utilisation de la librairie standard.

Ouvrir le fichier f_fin avec ifstream est-ce une bonne solution ?

Quelle différence avec fopen par exemple (il me semble que tout code C est compatible C++) ?
http://www.cplusplus.com/reference/cstdio/fopen/

Ou peut-être que je me trompe complètement, et qu’il n’existe que ifstream pour ouvrir un fichier binaire ? La documentation est très confuse par apport à ça je trouve.

@Green je te renvoie la question pourquoi ifstream ne serait pas une bonne solution ? ifstream est fait pour ça . Après oui tu peux utiliser des fonctions du C mais je ne vois pas l’utilité ça ne changera pas de grand chose les performances si c’est ça ta question. Après je pense que la différence entre fopen et l’ouverture du fichier par le biais d’ifstream est assez mince les deux font appel à l’appel système open . Je ne suis pas un expert sur cette question donc après pour avoir vraiment ce qui varie il faut chercher dans les documentations ^^

Si tu n’utilise pas de bibliothèque externe , ifstream me semble être la meilleure solution après tu peux utiliser du C mais je ne vois pas l’utilité.

+0 -0

je te renvoie la question pourquoi ifstream ne serait pas une bonne solution ? ifstream est fait pour ça . Après oui tu peux utiliser des fonctions du C mais je ne vois pas l’utilité ça ne changera pas de grand chose les performances si c’est ça ta question.

Ah d’accord, merci !
C’est que j’ai l’impression que la librairie standard du C++ contient milles arborescences sur la gestion des stream et je pensais qu’il existait peut-être une fonction plus appropriée, effectivement bien en regard des performances.

Du coup résolu. :lol:

Non, tout code C n’est pas forcément compatible C++. Je pense par exemple au designated initializer (pour initialiser les struct en utilisant le format {.field = value, .field2 = value2}) fait partie du C depuis C99 alors que ça n’a été standardisé en C++ que depuis C++20 (soit ~20 ans plus tard). Et ce n’est probablement pas le seul exemple. Au passage, fopen du header stdio.h, c’est du C. std::fopen du header cstdio, c’est du C++.

Pour réponse à la question, std::ifstream a une interface qui ressemble plus à du C++ (namespace, objets, surcharge d’opérateurs, plus de typage, …), alors que std::fopen ressemble plus à du C (fonctions et void*). J’aurais donc tendance à préférer std::ifstream, mais c’est plus une préférence que la vérité absolue.

A contrario de fopen(), *fstream sont des capsules RAII qui vont te garantir la restitution de la ressource fichier quelque que soit le chemin pris pour quitter la fonction courante. Si tu utilise les handles du C, il te faut rajouter une couche de unique_ptr avec deleter qui va bien pour être sûr de bien fermer ton fichier même s’il y a des exceptions.

Sinon, pour du binaire, les performances devraient se valoir. Tu vas perdre bien plus de temps à lire octet par octet -> il faut lire par bloc. Après avec un std::count tu auras vite fait de compter tes 0.

PS: La présence de std::feof() du C ou std::*stream::eof() dans une boucle du C++ est un signe fort de bug. Le flag de fin de fichier n’est positionné qu’au moment où la fin est atteinte. Pas avant.
<EDIT>On va utiliser eof pour distinguer les causes de fin: problème majeur dans le fichier (disque corrompu, clé arrachée…), ou fin atteinte, plus le cas: fichier contenant des informations pas compatibles avec ce qui est attendu dans le cas des lectures formatées (genre f >> un_entier qui n’est pas entier).</EDIT>

std::ifstream f(name, std::ios_base::binary); // "in" est implicite sur un ifstream
if (!f) throw std::runtime_error("cannot open file " + name);
size_t nb = 0;

char buffer[4096]; // Je ne sais jamais quelles sont les bonnes tailles à prendre
while (f.read(buffer, std::size(buffer)) {
    nb += count(std::begin(buffer), std::end(buffer), 0);
}
nb += count(std::begin(buffer), std::begin(buffer)+f.gcount(), 0); // pas besoin de tester eof à apriori
// pas besoin de f.close(), c'est implicite à la fin de la portée de cette fonction qui ne doit pas être plus longue, SRP oblige!


// ou avec readsome
for (std::size_t n; (n = f.readsome(buffer, std::size(buffer))) > 0; ) {
    nb += count(std::begin(buffer), std::begin(buffer)+n, 0)
}
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte