Comment convertir une structure big-endian en une petite structure endian?
j'ai un fichier binaire qui a été créé sur une machine unix. C'est juste un tas de documents écrits l'un après l'autre. L'enregistrement est défini à quelque chose comme ceci:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
}
j'essaie de comprendre comment je pourrais lire et interpréter ces données sur une machine Windows. J'ai quelque chose comme ceci:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
cout << "fooword = " << r.fooword << endl;
je reçois un tas de données, mais ce n'est pas les données que j'attends. Je suis suspect que mon problème a à voir avec la endian différence des machines, donc je viens demander à ce sujet.
je comprends que plusieurs octets seront stockées dans little-endian sur windows et big-endian dans un environnement unix, et je le conçois. Pour deux octets, 0x1234 sur windows sera 0x3412 sur un système unix.
Ne endianness affectent l'ordre des octets de la structure dans son ensemble, ou de chaque membre de la structure? Quelles approches dois-je adopter pour convertir une structure créée sur un système unix en une structure qui possède les mêmes données sur un système windows? Les liens qui sont plus en profondeur que de l'ordre des octets de quelques octets serait génial, trop!
8 réponses
modifier: si la structure a été écrite sans emballage, alors il devrait être assez simple. Quelque chose comme ceci (non testé) code:
// Functions to swap the endian of 16 and 32 bit values
inline void SwapEndian(UINT16 &val)
{
val = (val<<8) | (val>>8);
}
inline void SwapEndian(UINT32 &val)
{
val = (val<<24) | ((val<<8) & 0x00ff0000) |
((val>>8) & 0x0000ff00) | (val>>24);
}
ensuite, une fois que vous avez chargé la structure, échangez chaque élément:
SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
en Fait, endianness est une propriété du matériel sous-jacent, pas de l'OS.
la meilleure solution est de passer à un standard lors de l'écriture des données -- Google pour "commande octet réseau" et vous devriez trouver les méthodes pour le faire.
Edit: voici le lien: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html
ne pas lire directement dans struct à partir d'un fichier! L'empaquetage peut être différent, vous devez jouer avec pragma pack ou des constructions similaires spécifiques au compilateur. Trop peu fiable. Beaucoup de programmeurs s'en tirent très bien puisque leur code n'est pas compilé dans un grand nombre d'architectures et de systèmes, mais cela ne veut pas dire que c'est une bonne chose à faire!
une bonne approche alternative consiste à lire l'en-tête, quoi que ce soit, dans un tampon et à analyser à partir de trois pour éviter les entrées/sorties au-dessus dans atomic des opérations comme lire un entier 32 bits non signé!
char buffer[32];
char* temp = buffer;
f.read(buffer, 32);
RECORD rec;
rec.foo = parse_uint32(temp); temp += 4;
rec.bar = parse_uint32(temp); temp += 4;
memcpy(&rec.fooword, temp, 11); temp += 11;
memcpy(%red.barword, temp, 11); temp += 11;
rec.baz = parse_uint16(temp); temp += 2;
la déclaration de parse_uint32 ressemblerait à ceci:
uint32 parse_uint32(char* buffer)
{
uint32 x;
// ...
return x;
}
c'est une abstraction très simple, cela ne coûte rien de plus dans la pratique pour mettre à jour le pointeur:
uint32 parse_uint32(char*& buffer)
{
uint32 x;
// ...
buffer += 4;
return x;
}
la dernière forme permet de nettoyer le code pour l'analyse du buffer; le pointeur est automatiquement mis à jour lorsque vous analysez l'entrée.
de même, memcpy pourrait avoir un assistant, quelque chose comme:
void parse_copy(void* dest, char*& buffer, size_t size)
{
memcpy(dest, buffer, size);
buffer += size;
}
la beauté de ce type d'arrangement est que vous pouvez avoir l'espace de noms "little_endian" et "big_endian", alors vous pouvez le faire dans votre code:
using little_endian;
// do your parsing for little_endian input stream here..
facile à commuter endianess pour le même code, bien que, rarement nécessaire fonctionnalité.. les formats de fichier ont généralement une endianess fixe de toute façon.
ne pas l'abstraire en classe avec des méthodes virtuelles; ajouterait juste au-dessus, mais se sentent libres à Si ainsi incliné:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
Le lecteur objet serait évidemment juste un emballage mince pointer. Le paramètre Taille serait utilisé pour la vérification des erreurs, le cas échéant. Pas vraiment obligatoire pour l'interface en soi.
remarquez comment le choix d'endianess ici a été fait au moment de la COMPILATION (puisque nous créons l'objet little_endian_reader), donc nous invoquons la méthode virtuelle overhead sans raison particulière, donc je ne suivrais pas cette approche. ; -)
a ce stade, il n'y a pas de véritable raison de conserver le "fileformat" struct" autour de, vous pouvez organiser les données à votre convenance, et pas nécessairement le lire dans n'importe quel spécifiques struct à tous; après tout, c'est juste des données. Quand vous lisez des fichiers comme des images, vous n'avez pas vraiment besoin de l'en-tête autour.. vous devriez avoir votre conteneur d'image qui est le même pour tous les types de fichiers, donc le code pour lire un format spécifique devrait juste lire le fichier, interpréter et reformater les données et stocker la charge utile. =)
je veux dire, est-ce que ce regard compliqué?
uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();
le code peut avoir l'air si joli, et être vraiment bas au-dessus! Si l'endianess est la même pour le fichier et l'architecture pour lesquels le code est compilé, l'innerloop peut ressembler à ceci:
uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
cela pourrait être illégal sur certaines architectures, de sorte que l'optimisation pourrait être une mauvaise idée, et utiliser une approche plus lente, mais plus robuste:
uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
sur un x86 qui peut se compiler en bswap ou mov, ce qui est raisonnablement faible-overhead si la méthode est inlined; le compilateur insérerait le noeud "move" dans le code intermédiaire, rien d'autre, ce qui est assez efficace. Si l'alignement est un problème, la lecture complète-décalage-ou la séquence pourrait être générée, outch, mais pas trop mal. Comparer-branche pourrait permettre l'optimisation, si tester L'adresse LSB's et voir si peut utiliser la version rapide ou lente de l'analyse. Mais cela signifierait pénalité pour le test dans chaque lecture. Pourrait ne pas être en vaut la peine.
oh, d'accord, nous lisons les en-têtes et je ne pense pas que ce soit un goulot d'étranglement dans trop d'applications. Si un codec fait quelque innerloop vraiment serré, encore une fois, la lecture dans un tampon temporaire et le décodage de là est bien adviced. Même principe.. personne ne lit octet-à-temps à partir d'un fichier lors du traitement d'un grand volume de données. Eh bien, en fait, j'ai vu ce genre de code très souvent et la réponse habituelle à "pourquoi vous le faites" est que les systèmes de fichiers bloquent les lectures et que les octets viennent de la mémoire de toute façon, vrai, mais ils passent par une pile d'appels profonde qui est haute-au-dessus pour obtenir quelques bytes!
quand même, écrivez le code de l'analyseur une fois et utilisez zillion fois - > victoire épique.
lire directement dans struct à partir d'un fichier: ne le faites pas!
il affecte chaque membre indépendamment, pas l'ensemble struct
. Aussi, il n'affecte pas les choses comme des tableaux. Par exemple, il ne fait que des octets dans un int
s stocké dans l'ordre inverse.
PS. Cela dit, il pourrait y avoir une machine avec bizarre boutisme. Ce que je viens de dire s'applique à la plupart des machines utilisées (x86, ARM, PowerPC, SPARC).
Vous devez corriger l'endianess de chaque membre de plus d'un octet, individuellement. Les chaînes n'ont pas besoin d'être converties (fooword et barword), car elles peuvent être considérées comme des séquences d'octets.
cependant, vous devez vous occuper d'un autre problème: l'aligmenent des membres dans votre structure. Fondamentalement, vous devez vérifier si si sizeof(RECORD) est le même sur unix et le code windows. Les compilateurs fournissent généralement des données pragmatiques pour définir l'aligment que vous voulez (par exemple, le paquet #pragma).
vous devez également tenir compte des différences d'alignement entre les deux compilateurs. Chaque compilateur est autorisé à insérer du rembourrage entre les membres dans une structure qui convient le mieux à l'architecture. Si vous avez vraiment besoin de savoir:
- comment L'UNIX prog écrit au fichier
- S'il s'agit d'une copie binaire de l'objet, la disposition exacte de la structure.
- si c'est une copie binaire ce que l'endian-ness de l'architecture source.
C'est pourquoi la plupart des les programmes (Que j'ai vu (qui doivent être plate-forme neutre)) sérialiser les données dans un flux de texte qui peut être lu facilement par la norme iostreams.
j'aime à mettre en œuvre un SwapBytes méthode pour chaque type de données que les besoins de l'échange, comme ceci:
inline u_int ByteSwap(u_int in)
{
u_int out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[3] ;
outdata[3] = indata[0] ;
outdata[1] = indata[2] ;
outdata[2] = indata[1] ;
return out;
}
inline u_short ByteSwap(u_short in)
{
u_short out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[1] ;
outdata[1] = indata[0] ;
return out;
}
puis j'ajoute une fonction à la structure qui doit être échangée, comme ceci:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
void SwapBytes()
{
foo = ByteSwap(foo);
bar = ByteSwap(bar);
baz = ByteSwap(baz);
}
}
alors vous pouvez modifier votre code qui lit (ou écrit) la structure comme ceci:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
r.SwapBytes();
cout << "fooword = " << r.fooword << endl;
pour prendre en charge différentes plates-formes, vous avez juste besoin d'avoir une implémentation spécifique de chaque surcharge de ByteSwap.
quelque Chose comme ça devrait fonctionner:
#include <algorithm>
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UINT16 baz;
}
void ReverseBytes( void *start, int size )
{
char *beg = start;
char *end = beg + size;
std::reverse( beg, end );
}
int main() {
fstream f;
f.open( "file.bin", ios::in | ios::binary );
// for each entry {
RECORD r;
f.read( (char *)&r, sizeof( RECORD ) );
ReverseBytes( r.foo, sizeof( UINT32 ) );
ReverseBytes( r.bar, sizeof( UINT32 ) );
ReverseBytes( r.baz, sizeof( UINT16 )
// }
return 0;
}