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 !
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 !
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.
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.
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 ();
Salut,
Pourquoi utiliser une unionD'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
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 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)); }
qui pourra fonctionner avec tous type POD
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)); }
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
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.
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.
naïvement, en quoi la référence non constante aide le compilateur a déterminer le type ?
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)); }
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à une fonction dont la signature serait
Code : Sélectionner tout - Visualiser dans une fenêtre à part UnType foo(/* que des paramètres considérés comme constants */)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
Code : Sélectionner tout - Visualiser dans une fenêtre à part void foo (UnType & t) // t sera modifié dans la fonction -->effet de bord
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 à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
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 template <typename T> T read(std::ifstream & ifs)![]()
Bon, il y a tout à fait moyen de le faire, mais cela m'obligerait à écrire un code qui serait proche deTu 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.
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); /* ... */ }
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 delorsque 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 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 rienLe 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.
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 /* ... */ }
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
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
Partager