Fichiers binarys et compatibilité multiplateforme

J’ai écrit une bibliothèque C ++ qui enregistre mes données (une collection de structures personnalisées, etc.) dans un fichier binary. J’utilise actuellement (c’est-à-dire créer et consumr ) les fichiers localement, sur ma machine Windows (XP). Pour plus de simplicité, pensons à la bibliothèque en deux parties: un rédacteur (crée les fichiers) et un lecteur ou un consommateur (lit simplement les données des fichiers).

Récemment cependant, je voudrais également consumr (c.-à-d. Lire) les fichiers de données que j’ai créés sur ma machine XP, sur ma machine Linux. Je dois faire remarquer à ce stade que les deux machines sont des PC (ont donc le même endianess, etc.).

Je peux construire un lecteur (et comstackr pour Linux [Ubuntu 9.10 pour être précis]), puisque je suis le créateur de la bibliothèque. Ma question, avant de me lancer dans cette voie (de construire le lecteur, etc.) est la suivante:

En supposant que j’ai construit avec succès le lecteur pour Linux,

Puis-je copier simplement les fichiers créés sur la machine Windows (XP) sur la machine Linux (Ubuntu 9.10) et utiliser le lecteur Linux pour lire le fichier copié avec succès?

Pour que les fichiers soient compatibles binarys:

  • endianness doit correspondre (comme il le fait pour vous)
  • ordre d’emballage bitfield doit être le même
  • les tailles et la signature des types doivent être les mêmes
  • le compilateur doit prendre les mêmes décisions concernant le remplissage et l’alignement

Il est certainement possible que toutes ces conditions soient remplies ou que vous ne frappiez aucun cas pour lequel elles ne le sont pas. À tout le moins, cependant, j’appendais des contrôles de santé mentale et / ou des membres sentinelles pour détecter les problèmes.

Les fichiers binarys doivent être compatibles entre les ordinateurs dotés de la même version.

Le problème que vous pourriez avoir dans votre code est la taille de ints, vous ne pouvez pas nécessairement supposer que le compilateur sur différents systèmes d’exploitation a la même taille int. Donc, soit copier des blocs d’octets et les lancer, soit utiliser int16, int32, etc.

Les structures ne sont pas un format de fichier et vous ne devriez pas essayer de les utiliser en tant que telles.

Lorsque vous essayez de faire fonctionner des structures avec fread et fwrite , il existe un grand nombre de hacks pour le faire fonctionner. Vous transformez des entiers en octets afin de pouvoir partager des fichiers entre des machines little-endian et big-endian. Vous modifiez vos structures pour utiliser des types d’entiers à largeur fixe, vous pouvez donc partager des machines avec des tailles de mots différentes (par exemple, entre des machines x86 et x64). Vous ajoutez des pragmas spécifiques au compilateur pour contrôler le remplissage des structures à partager entre les versions du compilateur.

Ça marche, mais c’est moche. Sans oublier, facile à se tromper.

À l’instar de la recommandation dans L’erreur d’ordre des octets , une bien meilleure idée est d’écrire du code pour lire / écrire les champs individuellement. En écrivant votre propre code, vous pouvez vous assurer qu’il n’y a pas de remplissage, et vous pouvez choisir des tailles entières indépendamment de la taille locale des entiers, et prendre en charge les deux endianes sans swap (en lisant / écrivant les octets séparément).

Contrairement à l’approche pirate, il est difficile de se tromper. De plus, comme vous ne vous fiez à aucun comportement spécifique au compilateur ou à l’architecture, votre code fonctionnera sur tous les compilateurs et toutes les architectures, ou aucun. Si vous le faites correctement, vous ne devriez pas avoir de bogues spécifiques à la plate-forme.

Il y a un inconvénient; la lecture / écriture individuelle des champs sera plus lente que l’utilisation directe de fread / fwrite. Vous pouvez configurer un tampon ( uint8_t buffer[] ) et y écrire l’intégralité des données, puis tout écrire en une fois, ce qui peut aider, mais cela sera encore plus lent (car vous devez toujours vous déplacer les champs dans le tampon un par un), mais pour la plupart des objectives, il sera toujours assez rapide (exceptions étant des systèmes intégrés / temps réel ou un calcul extrêmement performant).

Si:

  • les machines ont la même endianess (comme vous l’avez dit) et
  • vous ouvrez les stream en mode binary, comme le mode texte peut faire des choses drôles, par exemple avec des fins de ligne et
  • vous avez programmé proprement pour ne pas trébucher sur des choses définies par l’implémentation telles que les alignements, les tailles de type de données et

alors oui, vos fichiers doivent être portables.

Le troisième point est ce qui rend un format de fichier «portable». Selon le type de données que vous avez dans vos structures, cela peut être très facile ou un peu compliqué. Les champs de bits, ou les données réinterprétées d’un type différent, sont particulièrement délicates.

Vous pouvez envisager de consulter la bibliothèque de sérialisation Boost . Beaucoup de reflection a été mise en place, et il va gérer plusieurs des incompatibilités potentielles entre plates-formes pour vous. Bien sûr, il est possible que ce soit excessif pour votre cas d’utilisation particulier, surtout si vous avez déjà implémenté vos rédacteurs et lecteurs.