Remplissage Struct 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 multi-plateforme/compilateur?

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

39
demandé sur Peter Mortensen 2011-03-22 23:29:29

5 réponses

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

Don Box écrit (citant son livre Essentielle COM, chapter COM Comme Un Meilleur C++)

C++ et portabilité


Une fois que la décision est prise de distribuer une Classe C++ en tant que DLL, un est confronté à l'un des fondamentaux faiblesses de C++, c'est-à-dire manque de standardisation au niveau binaire niveau. Bien que le projet ISO/ANSI C++ Le document de travail tente de codifier les programmes compileront et ce que le les effets sémantiques de leur exécution seront être, , il ne fait aucune tentative de normaliser le modèle d'exécution binaire de C++. Le première fois ce problème deviendra évident est quand un client essaie de lier par rapport à la bibliothèque d'importation de la DLL FastString un environnement de développement C++ autre que celui utilisé pour construire le FastString DLL.

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

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

Par exemple, voir 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

C'est-à-dire que les tailles sont différentes même si les deux structures ont les mêmes membres!

Le Code à Ideone: http://ideone.com/HGGVl

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

41
répondu Nawaz 2018-02-22 18:31:53

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

Je vais décrire comment construire la structure en utilisant des scalaires, mais vous devriez pouvoir utiliser des structures imbriquées, tant que vous appliqueriez la même conception pour chaque structure incluse.

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

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

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

Ensuite, pad la structure manuellement, de sorte qu'à la fin vous correspondrez 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 little ou big endian. Le meilleur moyen est de "permuter" tout l'élément in situ avant d'écrire ou après lecture de la structure, si le format de stockage ne correspond pas à l'endianess du système hôte.

14
répondu Lindydancer 2011-03-22 20:47:38

Non, il n'y a pas de 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 protocolbuffers de google) peuvent vous aider.

6
répondu Erik 2011-03-22 20:32:41

Longue histoire courte, non. Il n'y a pas de moyen indépendant de la plate-forme et conforme à la norme pour gérer le rembourrage.

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

Les types D'objets ont un alignement prescriptions (3.9.1, 3.9.2). Le l'alignement d'un type d'objet est un entier défini par l'implémentation valeur représentant un nombre d'octets; un objet est alloué à une adresse qui répond aux exigences alignement de son type d'objet.

Mais il continue à partir de là et serpente vers 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 entre différents compilateurs, ou même entre des modèles d'adresses (c'est-à-dire 32 bits/64 bits) sous le compilateur même.

Sauf si vous avez des exigences de performance vraiment sévères, vous pouvez envisager de stocker vos données sur un disque dans un format différent,comme les chaînes de caractères. De nombreux protocoles haute performance envoient tout en utilisant 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 ceci: "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 de cette façon ils peuvent éviter l'endian-ness et d'autres problèmes.

3
répondu John Dibling 2011-03-22 20:43:33

Vous pouvez utiliser quelque chose comme boost::serialization.

2
répondu Yippie-Ki-Yay 2011-03-22 20:31:28