c++ - smart - ¿Cómo convierto una estructura big-endian a una pequeña estructura endian?
programar contratos inteligentes (8)
¡No lea directamente en estructura desde un archivo! El embalaje puede ser diferente, tienes que jugar con pragma pack o construcciones específicas del compilador similares. Demasiado poco fiable. Muchos programadores se salen con la suya, ya que su código no está compilado en una gran cantidad de arquitecturas y sistemas, ¡pero eso no significa que esté bien hacerlo!
Un buen enfoque alternativo es leer el encabezado, lo que sea, en un búfer y analizar desde tres para evitar la sobrecarga de E / S en operaciones atómicas, como leer un entero de 32 bits sin signo.
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 declaración de parse_uint32 se vería así:
uint32 parse_uint32(char* buffer)
{
uint32 x;
// ...
return x;
}
Esta es una abstracción muy simple, no cuesta ningún extra en la práctica actualizar el puntero también:
uint32 parse_uint32(char*& buffer)
{
uint32 x;
// ...
buffer += 4;
return x;
}
La forma posterior permite un código más limpio para analizar el búfer; el puntero se actualiza automáticamente cuando se analiza desde la entrada.
Del mismo modo, memcpy podría tener un ayudante, algo como:
void parse_copy(void* dest, char*& buffer, size_t size)
{
memcpy(dest, buffer, size);
buffer += size;
}
La belleza de este tipo de arreglo es que puedes tener el espacio de nombres "little_endian" y "big_endian", entonces puedes hacer esto en tu código:
using little_endian;
// do your parsing for little_endian input stream here..
Sin embargo, es fácil cambiar la endianess por el mismo código, característica que rara vez se necesita. Los formatos de archivo generalmente tienen una endianess fija.
NO abstraiga esto en clase con métodos virtuales; solo agregaría gastos generales, pero no dude en hacerlo si así lo desea:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
El objeto lector obviamente sería solo una envoltura delgada alrededor del puntero. El parámetro de tamaño sería para la verificación de errores, en su caso. No es realmente obligatorio para la interfaz per se.
Observe cómo se hizo la elección de endianess aquí en el TIEMPO DE COMPILACIÓN (ya que creamos el objeto little_endian_reader), por lo que invocamos la sobrecarga del método virtual sin una razón particularmente buena, por lo que no optaría por este enfoque. ;-)
En esta etapa no hay ninguna razón real para mantener la "estructura de formato de archivo" tal como está, puede organizar los datos a su gusto y no necesariamente leerlos en ninguna estructura específica; después de todo, son solo datos. Cuando lee archivos como imágenes, realmente no necesita el encabezado ... debe tener su contenedor de imágenes que sea el mismo para todos los tipos de archivos, por lo que el código para leer un formato específico debe leer el archivo, interpretar y reformatear el archivo. datos y almacenar la carga útil. =)
Quiero decir, ¿esto parece complicado?
uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();
¡El código puede verse así de bonito, y ser realmente muy bajo! Si la endianess es la misma para el archivo y la arquitectura para la que se compila el código, el ciclo interno puede verse así:
uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
Eso podría ser ilegal en algunas arquitecturas, por lo que la optimización podría ser una mala idea y utilizar un enfoque más lento pero más robusto:
uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
En un x86 que puede compilarse en bswap o mov, que es razonablemente bajo, si el método está en línea; el compilador insertaría el nodo "mover" en el código intermedio, nada más, lo cual es bastante eficiente. Si la alineación es un problema, la secuencia-lectura-desplazamiento completa puede generarse, superarse, pero aún no estar tan mal. Compare-branch podría permitir la optimización, si prueba la dirección LSB y vea si puede usar la versión rápida o lenta del análisis. Pero esto significaría una penalización para la prueba en cada lectura. Puede que no valga la pena el esfuerzo.
Oh, cierto, estamos leyendo HEADERS y esas cosas, no creo que sea un cuello de botella en demasiadas aplicaciones. Si algún códec está haciendo un ciclo interno TAN TENIDO, una vez más, la lectura en un búfer temporal y la decodificación desde allí están bien informadas. El mismo principio ... nadie lee bytes a tiempo desde un archivo cuando procesa un gran volumen de datos. Bueno, en realidad, he visto ese tipo de código muy a menudo y la respuesta habitual a "por qué lo haces" es que los sistemas de archivos bloquean las lecturas y que los bytes provienen de la memoria de todos modos, es cierto, pero pasan por una pila de llamadas profunda que es de alta sobrecarga para obtener unos pocos bytes!
Aún así, escribe el código del analizador una vez y usa un millón de veces -> épica win.
Leyendo directamente en estructura desde un archivo: ¡NO LO HAGA!
Tengo un archivo binario que se creó en una máquina Unix. Es sólo un montón de registros escritos uno tras otro. El registro se define algo como esto:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
}
Estoy tratando de averiguar cómo leería e interpretaría estos datos en una máquina con Windows. Tengo algo como esto:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
cout << "fooword = " << r.fooword << endl;
Obtengo un montón de datos, pero no son los datos que espero. Sospecho que mi problema tiene que ver con la diferencia endiana de las máquinas, así que he venido a preguntar sobre eso.
Entiendo que múltiples bytes se almacenarán en little-endian en windows y big-endian en un entorno Unix, y entiendo eso. Para dos bytes, 0x1234 en windows será 0x3412 en un sistema unix.
¿La endianidad afecta el orden de bytes de la estructura en su conjunto, o de cada miembro individual de la estructura? ¿Qué enfoques tomaría para convertir una estructura creada en un sistema UNIX a una que tenga los mismos datos en un sistema Windows? ¡Cualquier enlace que sea más profundo que el orden de bytes de un par de bytes también sería genial!
Además de endian, debe tener en cuenta las diferencias de relleno entre las dos plataformas. En particular, si tiene matrices de caracteres de longitud impar y valores de 16 bits, es posible que encuentre diferentes números de bytes de relleno entre algunos elementos.
Edición: si la estructura fue escrita sin embalaje, entonces debería ser bastante sencilla. Algo como este código (no probado) debería hacer el trabajo:
// 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);
}
Luego, una vez que hayas cargado la estructura, simplemente intercambia cada elemento:
SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
Afecta a cada miembro de forma independiente, no a toda la struct
. Además, no afecta a cosas como matrices. Por ejemplo, solo hace bytes en un int
s almacenados en orden inverso.
PD. Dicho esto, podría haber una máquina con endianidad extraña. Lo que acabo de decir se aplica a las máquinas más utilizadas (x86, ARM, PowerPC, SPARC).
Algo como esto debería funcionar:
#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;
}
Debe corregir la endianess de cada miembro de más de un byte, individualmente. Las cadenas no necesitan convertirse (fooword y barword), ya que pueden verse como secuencias de bytes.
Sin embargo, debe ocuparse de otro problema: aligmenent de los miembros en su estructura. Básicamente, debe verificar si sizeof (RECORD) es el mismo en ambos códigos de Unix y Windows. Los compiladores suelen proporcionar pragmas para definir el alineamiento que desea (por ejemplo, #pragma pack).
En realidad, el endianness es una propiedad del hardware subyacente, no del sistema operativo.
La mejor solución es convertir a un estándar al escribir los datos: Google para "orden de bytes de red" y debe encontrar los métodos para hacerlo.
Edición: aquí está el enlace: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html
Me gusta implementar un método SwapBytes para cada tipo de datos que necesita intercambio, como este:
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;
}
Luego agrego una función a la estructura que necesita intercambio, como esta:
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);
}
}
Luego puede modificar su código que lee (o escribe) la estructura de esta manera:
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;
Para admitir diferentes plataformas, solo necesita tener una implementación específica de cada sobrecarga ByteSwap.
También hay que considerar las diferencias de alineación entre los dos compiladores. A cada compilador se le permite insertar el relleno entre los miembros en una estructura que mejor se adapte a la arquitectura. Así que realmente necesitas saber:
- Cómo el programa de UNIX se escribe en el archivo
- Si se trata de una copia binaria del objeto, el diseño exacto de la estructura.
- Si se trata de una copia binaria cuál es el carácter endémico de la arquitectura de origen.
Esta es la razón por la que la mayoría de los programas (que he visto (que deben ser neutrales a la plataforma)) serializan los datos como un flujo de texto que los iostreams estándar pueden leer fácilmente.