web-dev-qa-db-fra.com

Remplissage de structure en C ++

Si j'ai un struct en C++, n'y a-t-il aucun moyen de le lire/écrire en toute sécurité dans un fichier compatible multiplateforme/compilateur?

Parce que si je comprends bien, chaque compilateur "tamponne" différemment en fonction de la plate-forme cible.

49
Baruch

Non, ce n'est pas possible. C'est à cause du manque de standardisation de C++ au niveau binaire .

Don Box écrit (citant son livre Essential COM , chapitre COM As A Better C++)

C++ et portabilité


Une fois la décision prise de distribuer une classe C++ en tant que DLL, on est confronté à l'une des faiblesses fondamentales du C++ , qui est, manque de standardisation au niveau binaire . Bien que le projet de document de travail ISO/ANSI C++ tente de codifier les programmes à compiler et les effets sémantiques de leur exécution, , il ne tente pas de normaliser le modèle d'exécution binaire de C++ . La première fois que ce problème deviendra évident, c'est lorsqu'un client essaie de se lier à la bibliothèque d'importation de la DLL FastString à partir d'un environnement de développement C++ autre que celui utilisé pour créer la DLL FastString.

Le remplissage des structures est effectué différemment par différents compilateurs. Même si vous utilisez le même compilateur, l'alignement de l'emballage des structures peut être différent en fonction de ce que pack pragma vous utilisez.

Non seulement que si vous écrivez deux structures dont les membres sont exactement identiques, la différence seulement est que l'ordre dans lequel ils sont déclarés est différent, alors la taille de chaque structure peut être (et est souvent) différente.

Par exemple, voyez ceci,

struct A
{
   char c;
   char d;
   int i;
};

struct B
{
   char c;
   int i;
   char d;
};

int main() {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
}

Compilez-le avec gcc-4.3.4, et vous obtenez cette sortie:

8
12

Autrement dit, les tailles sont différentes même si les deux structures ont les mêmes membres!

Code chez Ideone: http://ideone.com/HGGVl

L'essentiel est que la norme ne parle pas de la façon dont le remplissage doit être fait, et donc les compilateurs sont libres de prendre toute décision et vous ne pouvez pas assumer tous les compilateurs prennent la même décision.

49
Nawaz

Si vous avez la possibilité de concevoir vous-même la structure, cela devrait être possible. L'idée de base est que vous devez le concevoir de sorte qu'il ne soit pas nécessaire d'y insérer des octets de pad. la deuxième astuce est que vous devez gérer les différences d'endianess.

Je vais décrire comment construire la structure à l'aide de scalaires, mais vous devriez pouvoir utiliser des structures imbriquées, à condition d'appliquer la même conception pour chaque structure incluse.

Tout d'abord, un fait fondamental en C et C++ est que l'alignement d'un type ne peut pas dépasser la taille du type. Si c'est le cas, il ne serait pas possible d'allouer de la mémoire à l'aide de malloc(N*sizeof(the_type)).

Disposez la structure, en commençant par les plus grands types.

 struct
 {
   uint64_t alpha;
   uint32_t beta;
   uint32_t gamma;
   uint8_t  delta;

Ensuite, complétez la structure manuellement, de sorte qu'à la fin, vous fassiez correspondre le plus grand type:

   uint8_t  pad8[3];    // Match uint32_t
   uint32_t pad32;      // Even number of uint32_t
 }

L'étape suivante consiste à décider si la structure doit être stockée au format petit ou grand-boutien. La meilleure façon est de "permuter" tous les éléments in sit avant d'écrire ou après avoir lu la structure, si le format de stockage ne correspond pas à l'endianess du système hôte.

19
Lindydancer

Non, il n'y a aucun moyen sûr. En plus du remplissage, vous devez gérer différents ordres d'octets et différentes tailles de types intégrés.

Vous devez définir un format de fichier et convertir votre structure vers et depuis ce format. Les bibliothèques de sérialisation (par exemple boost :: serialization ou les tampons de protocole de Google) peuvent vous aider.

7
Erik

Bref, non. Il n'existe aucun moyen indépendant de la plate-forme et conforme à la norme de traiter le rembourrage.

Le remplissage est appelé "alignement" dans la norme, et il commence à en discuter en 3.9/5:

Les types d'objets ont des exigences d'alignement (3.9.1, 3.9.2). L'alignement d'un type d'objet complet est une valeur entière définie par l'implémentation représentant un certain nombre d'octets; un objet est alloué à une adresse qui répond aux exigences d'alignement de son type d'objet.

Mais cela continue à partir de là et se termine dans de nombreux coins sombres de la norme. L'alignement est "défini par l'implémentation", ce qui signifie qu'il peut être différent selon les différents compilateurs, ou même entre les modèles d'adresse (c'est-à-dire 32 bits/64 bits) sous le compilateur même.

Sauf si vous avez des exigences de performances vraiment sévères, vous pouvez envisager de stocker vos données sur un disque dans un format différent, comme des chaînes de caractères. De nombreux protocoles haute performance envoient tout à l'aide de chaînes lorsque le format naturel peut être autre chose. Par exemple, un flux d'échange à faible latence sur lequel j'ai récemment travaillé envoie des dates sous forme de chaînes formatées comme suit: "20110321" et les heures sont envoyées de la même manière: "141055.200". Même si ce flux d'échange envoie 5 millions de messages par seconde toute la journée, ils utilisent toujours des chaînes pour tout, car ils peuvent ainsi éviter l'endianisme et d'autres problèmes.

3
John Dibling

Vous pouvez utiliser quelque chose comme boost::serialization.

2
Yippie-Ki-Yay