Flux, sérialisation, fichiers binaires...

Gros soucis de compréhension et de choix de conception, d'outils, etc.

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

Deux-trois détails sur ton code :

  • reinterpret_cast<char*>(p.name_.begin()) devrait être remplacé par p.name_.c_str(), il est fait pour ça.
  • La lecture des strings ne doit pas se faire de la manière dont tu le fais. D'ailleurs, le compilateur est sensé ne pas te l'autoriser : p.name_.c_str() donne un const char*. La solution est d'avoir un buffer assez grand et de mettre ensuite son contenu dans la std::string via une affectation.
  • Si l'enregistrement se fait sur une machine big-endian et que la lecture se fait sur une machine little-endian, tu vas avoir de très mauvaises surprises.

L'endianess c'est pas seulement l'ordre des bits :/ .

Ksass`Peuk

Pour le coup, je serais curieux de savoir ce que ça peut être si c'est pas l'ordre des bytes.

+0 -0

@Ksass`Peuk : à ma connaissance, l'ordre des bits ne change pas selon l'endianness. Ou plus exactement, tu n'arriveras pas à transférer un fichier (que ce soit par mail, clé USB ou quoi que ce soit d'utilisé à l'heure actuelle) avec un problème dans l'ordre des bits. Même si cette ordre peut être différent suivant les architectures ou suivant les notations, les seuls cas pour lesquels la question peut se poser, c'est lors de la conception du matériel ou de la programmation des circuits électroniques.

Pour un programmeur courant, le seul problème posé par l'endianness, c'est l'ordre des bytes.

Plusieurs choses :

p.name_.c_str() est la première chose qui m'est venue à l'esprit, or c_str() renvoie un const char* ce qui ne permet pas d'écrire, d'où le begin() qui lui donne un char*.

Je ne comprends pas pourquoi tu te contredis sur ton 2e point. Evidemment qu'il ne faut pas utiliser c_str(), pour la raison évoquée juste au dessus. Et je vois pas l'intérêt d'allouer 1 buffer supplémentaire suivi d'une copie de buffer, quand on peut directement remplir un std::string.

Par ailleurs le c_str() n'est pas adapté du tout pour quoi que ce soit ici, parce que le \0 à la fin ne nous est d'aucune utilité. data() est la fonction à utiliser si on souhaite absolument un const char*, tout en connaissant en O(1) la taille de la chaîne.

L'endianess concerne bien l'ordre des bytes et non des bits.

Si l'enregistrement se fait sur une machine big-endian

Sauf cas très rare, toutes les machines sur lesquelles on travaille de nos jours sont en little endian. Ca ne me pose aucun cas de conscience de présupposer une machine little endian (tant client que serveur). Mais il faut effectivement l'avoir en tête.

+0 -1

+1 Il me semble aussi que l'endianness est lié à l'ordre de lecture des octets, ex : 1 en entier sera toujours représenté sous cette forme en binaire "0000 0001" et pas "1000 0000", en tout cas je n'ai pas encore vu de cas ou le sens des bits est traité dans un ordre différent.

Tout n'est pas little endian, les échanges réseaux c'est du big endian (RFC1700).

PS: Je souris doucement quand je lis certains messages qui se donne à coeur joie sur la sérialisation manuelle des données, bonjour l’interopérabilité. Je ne vais pas revenir sur les solutions proposées qui génèrent soit des problèmes d'endianness ou taille des types scalaire ou encore qui plombe la RAM, mais franchement pour certains il faut sortir de la bulle "je soutiens mon copain qui fait du "bas niveau" en mettant +1 et je met -1 aux autres si je comprends pas trop ce qu'il dit", ayant déjà fait de la sérialisation (pas seulement fait dans le sens j'ai utilisé une API, mais étudié ce qu'implique un protocole de sérialisation) je pense que je suis loin d'être à coté de la plaque par rapport à certaines solutions proposées. Il faut être plus ouvert et ne pas se renfermer dans ce lobby parce que vous ne donnez vraiment pas envie à ce qu'on réponde aux sujets taggués C et C++.

+0 -0

(pour info à titre perso je n'ai pas mis de +1/-1 sur ce topic)

Qu'un échange réseau se fasse en big endian ne change strictement rien aux données qu'on manipule, qui sont encapsulées sans signification particulière dans des protocoles qui eux sont en little endian. Ce qui compte ce sont les machines. Après si vous faites du web, évidemment ça va jouer des tours, mais il ne me semble pas que ça soit le but ici.

Si y'a des solutions de sérialisation, dans Boost::Spirit notamment que je ne connais que peu, c'est cool et à conseiller. Perso j'ai du mal à me plonger dedans, mais si vous montrez des codes d'exemple pour que Ge0 puisse appréhender cette solution, c'est chouette et faites-le :) .

Pour régler le problème d'endianness, il suffit par exemple d'ajouter un BOM (Byte Order Mark) au début, et de coder la lecture en fonction. C'est juste plus de taff, qui devrait être fait selon moi après avoir eu une première version fonctionnelle. (mais ça c'est ma façon itérative de coder)

Il faut aussi penser à fixer les tailles des entiers plutôt que de les faire en automatique comme je le fait (en se restreignant à des int8_t, int16_t, int32_t, int64_t).

+0 -0

Plusieurs choses :

p.name_.c_str() est la première chose qui m'est venue à l'esprit, or c_str() renvoie un const char* ce qui ne permet pas d'écrire, d'où le begin() qui lui donne un char*.

Je ne comprends pas pourquoi tu te contredis sur ton 2e point. Evidemment qu'il ne faut pas utiliser c_str(), pour la raison évoquée juste au dessus. Et je vois pas l'intérêt d'allouer 1 buffer supplémentaire suivi d'une copie de buffer, quand on peut directement remplir un std::string.

Par ailleurs le c_str() n'est pas adapté du tout pour quoi que ce soit ici, parce que le \0 à la fin ne nous est d'aucune utilité. data() est la fonction à utiliser si on souhaite absolument un const char*, tout en connaissant en O(1) la taille de la chaîne.

germinolegrand

Utiliser reinterpret_cast pour transformer un itérateur en pointeur, ça n'a pas plus de chance de fonctionner que transformer une std::string en char* : c'est deux choses totalement différentes. D'ailleurs, j'ai essayé et le compilateur (que ce soit clang ou gcc) le refuse clairement (ce qui n'a rien de choquant) :

error: reinterpret_cast from 'iterator' (aka […]) to 'char *' is not allowed

Si l'interface de std::string ne te donne aucun accès en écriture au buffer utilisé, c'est pour une très bonne raison. Par exemple, une fois que tu as fait ton read (par exemple via un const_cast sur le c_str()), tu penses que tu aura quoi quand tu fera size()? Perso, je plancherais sur l'ancienne taille de la string (et un test rapide me le confirme). Alors, certes, c'est moins efficace de créer un buffer et de faire deux copies (une fois dans le buffer et une fois dans la string), mais au moins, c'est prévu par le standard et ça ne provoquera jamais de bugs.

Je viens de me documenter un peu et je viens de voir que le standard restreint beaucoup plus l'implémentation des std::string que je ne le pensais (du moins, à partir du C++11). Du coup, je valide ce que tu dis à propos de &*str.begin() et &str[0], en précisant que ça ne fonctionne que si l'on n'essaye pas de modifier après le \0 final (à ne pas toucher non plus).

Et au passage, je retire ce que j'ai dit sur le dernier paragraphe de mon message précédent.

Je ne vais pas revenir sur les solutions proposées qui génèrent soit des problèmes d'endianness ou taille des types scalaire ou encore qui plombe la RAM.

A vrai dire ça n'est pas le souci. Mais ça je pensais qu'il était inutile de le mentionner.

C'est comme si tu me disais de passer à Linux sous prétexte que j'ai des problèmes sous mon OS préféré (Windows). Je grossis le trait, mais pour moi, c'est ça.

Et inutile de partir dans un débat inutile sur l'endianess et autres joyeusetés. C'est un problème auquel j'ai déjà fait face. Donc je vous saurais gré d'éviter la masturbation intellectuelle puisque germinolegrand a été le seul capable de résoudre mon problème, quand bien même ma question était mal tournée. :)

Donc oui, les autres, je vous ai mis des -1, n'en déplaise à votre ego.

Edit : Et la solution proposée par RomHa Korev reste à étudier. Je le précise car Protocol Buffer semble être un bon compromis pour ce que je cherche à faire.

+0 -0

L'endianess concerne bien l'ordre des bytes et non des bits.

germinolegrand

Je me permets juste de corriger ce point : l'endianess concerne également les bits, notamment lors de communications réseau. Si sur un réseau IP, la question ne se pose pas, sur certaines liaison de données, l'ordre des bits peut être inversé (la notion d'octets ou de bytes n'ayant pas forcément lieu d'être). Il n'est d'ailleurs pas si rare d'accumuler des endianness différents sur les mots, les bytes et les bits… De mémoire, on retrouve également cette notion d'endianness dans certains chipsets.

Geo ne fait pas de sérialisation.

Stranger

Bon sang mais c'est exactement là le souci, pour certains vous en venez à essayez de détourner la réalité des choses (peut-être pour mieux vous sentir je ne sais pas), on ne parle que de sérialisation/désérialisation depuis le début.

En informatique, la sérialisation […] est un processus visant à coder l'état d'une information qui est en mémoire sous la forme d'une suite d'informations […] le plus souvent des octets voire des bits. Cette suite pourra par exemple être utilisée pour la sauvegarde (persistance) […].

Wikipédia


C'est comme si tu me disais de passer à Linux sous prétexte que j'ai des problèmes sous mon OS préféré (Windows). Je grossis le trait, mais pour moi, c'est ça.

Ge0

… et bien sûr à vous donner des excuses pour le moins bidon lorsqu'on vous sort les problématiques liés au besoin. Le problème c'est qu'on peut jouer des heures à essayer de contredire la personne en face. Moi je ne t'invite nullement à passer sous Linux, mais à prévoir le cas où toi ou ton fournisseur de donnée seraient par exemple forcé d'effectuer une migration (soit en changeant de machine et donc de processeur ou en passant d'un Windows XP à Windows 10 par exemple) en utilisant un protocole (existant) suffisamment fiable sans forcément essayer de réinventer le monde avec des solutions maison comme certains le proposent à coup de mmap, qui au passage est une solution POSIX dispo sous Linux. Tout le monde n'est pas visé dans ce que je dis là, les personnes qui partagent un sentiment de rejet récurrent envers les autres technos principalement (qui ne l’expriment pas explicitement), qui se réveilleront peut-être le jour où ils ne seront plus que 10 aficionados à faire ce qu'ils font pour enfin avoir l'esprit d'ouverture. Sur ce j'arrête avec mes messages "inutiles".

+0 -1

Non, parce que le fichier à parser est peut-être un exécutable compilé en 1969 pour l'ordinateur de bord d'Apollo, une image d'un système de fichiers corrompu, un dump d'un outil propriétaire quelconque ou un jeu vidéo antique que Geo voudrait émuler. Le postulat de départ est qu'on ne peut pas changer le format d'entrée.

C'est exactement ça. J'ai juste fait l'erreur de ne pas le préciser, je pense.

Et au passage, le format PNG, il contient des entiers encodés sous la forme gros-boutiste - big-endian en Anglais. Ca non plus on ne peut pas le changer.

Mais bon, trop occupés à parler de lib déjà faites, de JSON/XML/… Que d'aiguiller sur une solution technique pour résoudre un problème trivial.

Au bout d'un moment faudra accepter qu'il faille se soucier de détails de ce genre. D'accord, il faut éviter de trop creuser au point de se soucier du nombre d'atomes par transistor, mais quand même. :/

De toute façon germinolegrand et Stranger ont largement répondu à ma question, donc il est inutile d'en rajouter.

Double post.

Au passage, je tenais à dire que j'étais désolé si j'avais parlé de serialisation alors que le terme était mal choisi. Je sais maintenant qu'il s'agit de stocker de l'information sous sa véritable forme, ni plus ni moins. :)

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