IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

format de fichier binaire


Sujet :

Langage C++

  1. #1
    Membre confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    120
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2003
    Messages : 120
    Billets dans le blog
    1
    Par défaut format de fichier binaire
    Bonjour,

    je m’intéresse à la création de format de fichier binaire ainsi que l'utilisation, auriez-vous des conseils, des ressources, des exemples, pour en créer un de toute pièce ?

    ça me permettra aussi de progresser en C++

    merci !

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    Salut,

    Un fichier, binaire ou pas, ça se manipule via un std::fstream, std::ofstream pour output et écrire dedans via write ou les opérateurs <<.
    ifstream pour input et la lecture, les fonctions read et opérateurs >>.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 394
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 394
    Par défaut
    Mon conseil: Prends exemple sur le format PNG: Un format séparé en sections, chacune précédée d'un tag identifiant son type et de sa taille.
    S'assurer que chaque section commence par sa taille, et que celle-ci ait elle-même un emplacement fixe et une taille fixe (32 bits minimum, 64 bits recommandé) ou au moins semi-fixe (en gros, il ne faut pas que tu aies besoin de "lire jusqu'à ce que ce ne soit plus un chiffre" comme dans l'horreur qu'est HTTP), t'épargnera bien des migraines.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  4. #4
    Membre confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    120
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2003
    Messages : 120
    Billets dans le blog
    1
    Par défaut
    merci pour vos conseils !!!

    dans mes recherches, j'ai cherché une solution pour écrire un float dans un fichier de façon binaire, que pensez-vous de ça ? :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
        union
        {
          float float_u4;
          char char_u4[4];
        };
     
        float_u4 = 1.234f;
     
        std::ofstream ofile;
        ofile.open ("test.bin", std::ofstream::binary);
        ofile.write (&char_u4[0], sizeof(char_u4));
        ofile.close ();
     
        std::ifstream ifile;
        ifile.open ("test.bin", std::ofstream::binary);
        ifile.read (&char_u4[0], sizeof(char_u4));
        ifile.close ();

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,

    Pourquoi utiliser une union D'autant plus que tu ne peux jamais être tout à fait sûr du nombre de bytes utilisés par un float...

    utilises les méthodes de conversion "classiques" (reinterpret_cast dans le cas présent) sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    float read(std::ifstream & ifs){
         float f;
         ifs.read(reinterpret_cast<char*>(&f), sizoef(f));
         return f;
    }
    void write(std::ofstream & ofs, float const f){
        ofs.write(reinterpret_cast<char>(&f), sizof(f));
    }
    Tu pourrais même envisager de le faire de manière générique, sous une forme qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* pour aider le compilateur à déterminer le type de l'élément qu'il doit lire, nous pouvons envisager
     * de transmettre celui-ci en sous forme d'une référence non constante
     */
    template <typename T>
    void read(std::ifstream & ifs, T & value){
        ifs.read(reinterpret_cast<char*>(&value), sizeof(T));
    }
    void write(std::ofstream & ofs, T const & value){
        ofs.write(reinterpret_cast<char*>(&value), sizeof(T));
    }
    qui pourra fonctionner avec tous type POD

    En cas de besoin, nous pourrons prévoir une spécialisation de cette fonction pour les types récurrents qui ne le sont pas (comme std::string, par exemple), mais ca, c'est une autre histoire

    Quoi qu'il en soit, le code serait bien plus simple, non

    Ah, et une dernière petite remarque: les fonction open et close n'ont normalement pas besoin d'être utilisées: le constructeur des flux (qu'ils soient d'entrée ou de sortie) est tout à fait en mesure d'ouvrir automatiquement un fichier, y compris en mode binaire, et leur destructeur le ferme automatiquement lorsque l'on quitte la portée dans lequel le flux est déclaré.

    Une fonction main qui écrirait une donnée dans un flux binaire et qui le lirait tout de suite après pourrait donc prendre une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int main(){
        {
            std::ofstream ofs("fichier.bin",std::ios_base::binary); // ofs est automatiquement ouvert ici
            /* pour bien faire, il faudrait s'assurer que le fichier est bien ouvert, mais bon */
            float w{3.1415926};
            write(ofs, w);
        } // ofs est automatiquement fermé ici
        float r;
        {
            std::ifstream ifs("fichier.bin", std::ios_base::binary); // ifs est automatiquement ouvert ici
            /* pour bien faire, il faudrait s'assurer que le fichier est bien ouvert, mais bon */
            read(ifs, r);
        } // ifs est automatiquement fermé ici
        std::cout<<"r: "<<r<<"\n";
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 394
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 394
    Par défaut
    Alors le gros problème des float, c'est que le format d'un float n'est pas défini par le standard C++, et donc rien n'est garanti marcher partout!
    Mais le "union-cast" devrait marcher sur la plupart des plate-formes qui utilisent des flottants IEEE 754.

    Autre soucis: L'endianness doit être choisie et documentée, et le code de lecture de ton implémentation de référence doit marcher quelle que soit l'endianness de la machine pour laquelle il est compilé (que ce soit grâce à des #ifdef ou via un code indépendant de l'endianness de l'hôte). Pour ton format, que choisir?
    • Le "standard" pour la communication, alias "Network Byte Order", est le big-endian. La norme POSIX (et l'API des sockets, respectée plus ou moins par Windows) contient des fonctions pour aider à convertir entre big-endian et "endianness de l'hôte", et rien de tout ça pour le little-endian.
    • Mais toutes les plate-formes grand public actuelles (principalement x86 et Android) utilisent little-endian. Si ton code de lecture/écriture est basé sur des #ifdef selon l'architecture ciblée, ça rend le code de lecture trivial (et donc plus rapide) pour les plate-formes little-endian.
    • Certains formats acceptent les deux, avec un champ indiquant quelle endianness ils utilisent.* L'intention est que le fichier soit systématiquement écrit dans l'endianness du code qui l'écrit, et lu la plupart du temps par la même endianness, avec un code "de secours" (pouvant être plus lent) utilisé quand un fichier provient d'une architecture d'endianness opposée.

    De nos jours, ma recommandation est de choisir "little endian partout". Ma préférence pour le code est de faire un code indépendant de l'endianness de l'hôte, mais pour un débutant ça peut être compliqué, et avoir simplement un code pour architectures big-endian et un code trivial pour architectures little-endian peut être mieux.
    Pour info, les BinaryReader et BinaryWriter du Framework .Net utilisent little-endian systématiquement.

    *Ce qui nous amène à la question des formats auto-documentés, que j'aborde ci-après.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 394
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 394
    Par défaut
    La question suivante: Veux-tu que ton format binaire puisse être auto-documenté?
    C'est-à-dire, qu'il puisse comporter une section expliquant les types et tailles des différents champs qu'il comporte? (voire qu'il ait un indicateur de type avant chaque donnée, même si ça bouffe plus de place?

    Cela permet de faire de la validation sur le contenu, d'être plus résistant à une mise à jour du format, etc. Mais c'est plus compliqué.

    Et pour finir, la question la plus importante:
    Veux-tu vraiment créer un format de toute pièces, et pourquoi?
    Tu auras droit à une meilleure compatibilité si tu implémentes à la place un format "générique" existant.
    Récemment, dans un de mes programmes en C#, j'ai implémenté du code pour lire le format de sérialisation binaire de .Net, ainsi que le format des PropertySet de Microsoft...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Membre confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    120
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2003
    Messages : 120
    Billets dans le blog
    1
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* pour aider le compilateur à déterminer le type de l'élément qu'il doit lire, nous pouvons envisager
     * de transmettre celui-ci en sous forme d'une référence non constante
     */
    template <typename T>
    void read(std::ifstream & ifs, T & value){
        ifs.read(reinterpret_cast<char*>(&value), sizeof(T));
    }
    void write(std::ofstream & ofs, T const & value){
        ofs.write(reinterpret_cast<char*>(&value), sizeof(T));
    }
    naïvement, en quoi la référence non constante aide le compilateur a déterminer le type ?

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par Katian Voir le message
    naïvement, en quoi la référence non constante aide le compilateur a déterminer le type ?
    Il faut savoir que, de manière générale, on préférera éviter les fonctions qui occasionnent des effet de bord.

    Si bien que l'on préférera généralement avoir une fonction dont la signature est
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    UnType foo(/* que des paramètres considérés comme constants */)
    à une fonction dont la signature serait
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void foo (UnType & t) // t sera modifié dans la fonction -->effet de bord
    Malheureusement, si ce principe est admis par tous, la programmation générique (comprends : l'utilisation des template) est là pour venir foutre un grand coup de pied dans la fourmilière

    El faut en effet savoir que le compilateur attend de savoir par quel type réel il doit remplacer tous les paramètre template d'une fonction pour générer le code binaire adapté. Mais, du coup, si j'écris une fonction dont la signature ressemblerait à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename T>
    T read(std::ifstream & ifs)
    comme le type de retour n'est pas pris en compte dans la signature, je fais comment, moi, pour indiquer au compilateur qu'il doit lire un double ou un point

    Bon, il y a tout à fait moyen de le faire, mais cela m'obligerait à écrire un code qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        double d = read<double>(ifs);
        Point p = read<Point>(ifs);
        UnType t = read<UnType>(ifs);
        /* ... */
    }
    Tu avoueras que les répétitions de type (pour déclarer la variable, puis pour indiquer qu'on veut lire une donnée du type de la variable) rendent le code redondant et peu facile à lire.

    Tant qu'à faire, j'aimerais autant que le compilateur puisse déterminer automatiquement le type de la donnée qui devra lire. Or, le compilateur est en mesure de déterminer le type de tous les éléments qui lui sont passés en paramètre.

    En effet, si j'écris une fonction template qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename T>
    void foo(T value)// que value soit passé par valeur, par référence (constante ou non) ou par pointeur ne changera rien
    lorsque j'appelle cette fonction en lui fournissant un paramètre, par exemple sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        double d;
        foo(d); // le compilateur comprend que le paramètre de foo est un double
        Point p;
        foo(p); // et là, il comprend que le paramètre est un point
        /* ... */
    }
    Le compilateur est en mesure de déterminer le type du paramètre transmis à foo, car il connait le type de la donnée qui sert d'argument dans la fonction appelante.

    Du coup, même si cela sous entend que notre fonction occasionnera un effet de bord du fait que la valeur de l'élément qui sert de paramètre dans la fonction appelante sera changée après que l'on ait appelé la fonction, on préférera sans doute occasionner un effet de bord (somme toutes limité), mais se faciliter la vie dans le reste du code

    Ou, du moins, il faut avouer que cette justification au fait d'occasionner un effet de bord est des plus sensées
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Personnellement, c'est le type de fonction que j'écris dans les 2 versions, car chacune possède des avantages et des inconvénients.

    - auto d = read<double>(ifs) permet de rendre d constant et l'usage de auto élimine la répétition de type.
    - double d; read(ifs, d) permet de réutiliser d en profitant du contexte déjà présent. Pour un double aucun avantage, mais pour un vecteur on peut ajouter les éléments en fin ou réduire les allocations lorsqu'on le ré-initialise s'il possède déjà des éléments. On peut aussi ajouter des modificateurs d'IO sans changer le prototype: std::string s; read(ifs, std::quoted(s)).
    - double d; read<d>(ifs) parce que c'est rigolo .

  11. #11
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Personnellement, c'est le type de fonction que j'écris dans les 2 versions, car chacune possède des avantages et des inconvénients.

    - auto d = read<double>(ifs) permet de rendre d constant et l'usage de auto élimine la répétition de type.
    - double d; read(ifs, d) permet de réutiliser d en profitant du contexte déjà présent. Pour un double aucun avantage, mais pour un vecteur on peut ajouter les éléments en fin ou réduire les allocations lorsqu'on le ré-initialise s'il possède déjà des éléments. On peut aussi ajouter des modificateurs d'IO sans changer le prototype: std::string s; read(ifs, std::quoted(s)).
    - double d; read<d>(ifs) parce que c'est rigolo .
    Ouaip, tu as tout à fait raison sur ce coup.

    Et c'est d'autant plus marrant qu'on peut alors s'arranger pour que la version à un paramètre fasse appel à la version avec effet de bord, histoire de ne pas répéter le code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename T>
    void read(std::istream & ifs, T & value){
        /* ce qui doit être fait */
    }
    template <typename T>
    T read(std::istream & ifs){
        T temp;
        read(ifs, temp);
        return temp;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Recopié un fichier au format U en binaire sous ZOS
    Par Kyle Katarn dans le forum z/OS
    Réponses: 6
    Dernier message: 12/11/2008, 09h49
  2. Ecriture fichier binaire : Format TIFF
    Par xChRiSx dans le forum C++
    Réponses: 2
    Dernier message: 12/04/2007, 18h39
  3. [Debutant] Comment lire la taille d'un fichier binaire ?
    Par Invité dans le forum Entrée/Sortie
    Réponses: 4
    Dernier message: 18/12/2003, 19h20
  4. communication fichier binaire fichier txt
    Par micdie dans le forum C
    Réponses: 3
    Dernier message: 05/12/2002, 00h19
  5. fichier binaire ou texte
    Par soussou dans le forum C++Builder
    Réponses: 4
    Dernier message: 14/06/2002, 13h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo