usar txt texto que los leer hacen guardar generar datos como binarios binario archivos archivo c++ casting binary

texto - guardar y leer datos en un archivo.txt en c++



Analizar un archivo binario. ¿Qué es una forma moderna? (9)

Actualmente lo hago así:

  • cargar archivo a ifstream

  • leer esta secuencia a char buffer [2]

  • convertirlo en unsigned short : unsigned short unsigned short len{ *((unsigned short*)buffer) }; . Ahora tengo la longitud de una cuerda.

El último riesgo SIGBUS un SIGBUS (si la matriz de caracteres comienza en una dirección impar y su CPU solo puede leer valores de 16 bits alineados en una dirección par), el rendimiento (algunas CPU leerán valores desalineados pero más lentos; otros como el moderno Los x86 son finos y rápidos) y / o problemas de endianness . Sugeriría leer los dos caracteres, entonces puedes decir (x[0] << 8) | x[1] (x[0] << 8) | x[1] o viceversa, usando htons si es necesario corregir la endianidad.

  • lee un flujo al vector<char> y crea una std::string partir de este vector . Ahora tengo id de cadena.

No es necesario ... solo lee directamente en la cadena:

std::string s(the_size, '' ''); if (input_fstream.read(&s[0], s.size()) && input_stream.gcount() == s.size()) ...use s...

  • de la misma manera, read siguientes 4 bytes y conviértalos a unsigned int . Ahora tengo un paso. while no es el final del archivo, read float s de la misma manera: cree un char bufferFloat[4] y cast *((float*)bufferFloat) para cada float .

Es mejor leer los datos directamente sobre las unsigned int y floats unsigned int , ya que de esa manera el compilador asegurará la alineación correcta.

Esto funciona, pero para mí se ve feo. ¿Puedo leer directamente a unsigned short o float unsigned short o string etc. sin crear char [x] ? Si no, ¿cuál es la forma de emitir correctamente (leí el estilo que uso, es un estilo antiguo)?

struct Data { uint32_t x; float y[6]; }; Data data; if (input_stream.read((char*)&data, sizeof data) && input_stream.gcount() == sizeof data) ...use x and y...

Tenga en cuenta que el código anterior evita la lectura de datos en arreglos de caracteres potencialmente no alineados, en donde no es seguro reinterpret_cast datos de un conjunto de caracteres potencialmente no alineados (incluso dentro de una std::string ) debido a problemas de alineación. Una vez más, es posible que necesite una conversión posterior a la lectura con htonl si existe la posibilidad de que el contenido del archivo difiera en su carácter endiano. Si hay una cantidad desconocida de float , deberá calcular y asignar suficiente almacenamiento con una alineación de al menos 4 bytes, luego apunte un Data* a ella ... es legal indexar más allá del tamaño de matriz declarado de y siempre ya que el contenido de la memoria en las direcciones a las que se accedió fue parte de la asignación y tiene una representación float válida leída desde el flujo. Más simple, pero con una lectura adicional, posiblemente más lenta, lea el uint32_t primero y luego el new float[n] y siga read allí ...

En la práctica, este tipo de enfoque puede funcionar y una gran cantidad de bajo nivel y código C hace exactamente esto. Las bibliotecas de alto nivel "más limpias" que pueden ayudarlo a leer el archivo deben estar haciendo algo similar internamente ...

Tengo un archivo binario con algún diseño que conozco. Por ejemplo, deje que el formato sea así:

  • 2 bytes (sin signo corto) - longitud de una cadena
  • 5 bytes (5 x caracteres) - la cadena - algún nombre de identificación
  • 4 bytes (int sin signo) - un paso
  • 24 bytes (6 x flotador - 2 pasos de 3 flotantes cada uno) - datos flotantes

El archivo debería verse (agregué espacios para facilitar la lectura):

5 hello 3 0.0 0.1 0.2 -0.3 -0.4 -0.5

Aquí 5 - es de 2 bytes: 0x05 0x00. "hola" - 5 bytes y así sucesivamente.

Ahora quiero leer este archivo. Actualmente lo hago así:

  • cargar archivo a ifstream
  • leer esta secuencia a char buffer[2]
  • convertirlo en corto sin firmar: corto unsigned short len{ *((unsigned short*)buffer) }; . Ahora tengo la longitud de una cuerda.
  • lee un flujo al vector<char> y crea una std::string partir de este vector. Ahora tengo id de cadena.
  • de la misma manera, lea los siguientes 4 bytes y conviértalos a int sin signo. Ahora tengo un paso.
  • mientras no finalice la lectura de archivos, flote de la misma manera: cree un char bufferFloat[4] y lance *((float*)bufferFloat) para cada float.

Esto funciona, pero para mí se ve feo. ¿Puedo leer directamente a unsigned short o float unsigned short o string etc. sin crear char [x] ? Si no, ¿cuál es la forma de emitir correctamente (leí el estilo que uso, es un estilo antiguo)?

PD: mientras escribía una pregunta, la explicación más clara que se planteaba en mi cabeza: ¿cómo emitir un número arbitrario de bytes desde una posición arbitraria en char [x] ?

Actualización: olvidé mencionar explícitamente que la longitud de los datos de cadena y flotante no se conoce en el momento de la compilación y es variable.


Dado que todos sus datos son variables, puede leer los dos bloques por separado y seguir utilizando la conversión:

struct id_contents { uint16_t len; char id[]; } __attribute__((packed)); // assuming gcc, ymmv struct data_contents { uint32_t stride; float data[]; } __attribute__((packed)); // assuming gcc, ymmv class my_row { const id_contents* id_; const data_contents* data_; size_t len; public: my_row(const char* buffer) { id_= reinterpret_cast<const id_contents*>(buffer); size_ = sizeof(*id_) + id_->len; data_ = reinterpret_cast<const data_contents*>(buffer + size_); size_ += sizeof(*data_) + data_->stride * sizeof(float); // or however many, 3*float? } size_t size() const { return size_; } };

De esa manera puedes usar la respuesta del Sr. kbok para analizar correctamente:

const char* buffer = getPointerToDataSomehow(); my_row data1(buffer); buffer += data1.size(); my_row data2(buffer); buffer += data2.size(); // etc.


De hecho, implementé un analizador de formato binario rápido y sucio para leer archivos .zip (siguiendo la descripción del formato de Wikipedia) el mes pasado, y siendo moderno decidí usar plantillas de C ++.

En algunas plataformas específicas, una struct empaquetada podría funcionar, sin embargo, hay cosas que no se manejan bien ... como campos de longitud variable. Sin embargo, con las plantillas, no existe tal problema: puede obtener estructuras arbitrariamente complejas (y tipos de devolución).

Un archivo .zip es relativamente simple, afortunadamente, así que implementé algo simple. La parte superior de mi cabeza:

using Buffer = std::pair<unsigned char const*, size_t>; template <typename OffsetReader> class UInt16LEReader: private OffsetReader { public: UInt16LEReader() {} explicit UInt16LEReader(OffsetReader const or): OffsetReader(or) {} uint16_t read(Buffer const& buffer) const { OffsetReader const& or = *this; size_t const offset = or.read(buffer); assert(offset <= buffer.second && "Incorrect offset"); assert(offset + 2 <= buffer.second && "Too short buffer"); unsigned char const* begin = buffer.first + offset; // http://commandcenter.blogspot.fr/2012/04/byte-order-fallacy.html return (uint16_t(begin[0]) << 0) + (uint16_t(begin[1]) << 8); } }; // class UInt16LEReader // Declined for UInt[8|16|32][LE|BE]...

Por supuesto, el OffsetReader básico tiene un resultado constante:

template <size_t O> class FixedOffsetReader { public: size_t read(Buffer const&) const { return O; } }; // class FixedOffsetReader

y dado que estamos hablando de plantillas, puede cambiar los tipos en el tiempo libre (podría implementar un lector proxy que delegue todas las lecturas en un shared_ptr que las memorice).

Lo que es interesante, sin embargo, es el resultado final:

// http://en.wikipedia.org/wiki/Zip_%28file_format%29#File_headers class LocalFileHeader { public: template <size_t O> using UInt32 = UInt32LEReader<FixedOffsetReader<O>>; template <size_t O> using UInt16 = UInt16LEReader<FixedOffsetReader<O>>; UInt32< 0> signature; UInt16< 4> versionNeededToExtract; UInt16< 6> generalPurposeBitFlag; UInt16< 8> compressionMethod; UInt16<10> fileLastModificationTime; UInt16<12> fileLastModificationDate; UInt32<14> crc32; UInt32<18> compressedSize; UInt32<22> uncompressedSize; using FileNameLength = UInt16<26>; using ExtraFieldLength = UInt16<28>; using FileName = StringReader<FixedOffsetReader<30>, FileNameLength>; using ExtraField = StringReader< CombinedAdd<FixedOffsetReader<30>, FileNameLength>, ExtraFieldLength >; FileName filename; ExtraField extraField; }; // class LocalFileHeader

Esto es bastante simplista, obviamente, pero increíblemente flexible al mismo tiempo.

Un eje obvio de mejora sería mejorar el encadenamiento, ya que aquí existe el riesgo de superposiciones accidentales. Sin embargo, el código de lectura de mi archivo funcionó la primera vez que lo probé, lo cual era suficiente evidencia para mí de que este código era suficiente para la tarea en cuestión.


Debería declarar mejor una estructura (con relleno de 1 byte - cómo - depende del compilador). Escribe usando esa estructura, y lee usando la misma estructura. Ponga solo POD en la estructura y, por lo tanto, no std::string etc. Use esta estructura solo para la E / S de archivos u otra comunicación entre procesos: use la struct normal o la class para guardarla para su uso posterior en el programa C ++.


La forma C, que funcionaría bien en C ++, sería declarar una estructura:

#pragma pack(1) struct contents { // data members; };

Tenga en cuenta que

  • Necesitas usar un pragma para hacer que el compilador alinee los datos como se ve en la estructura;
  • Esta técnica solo funciona con los tipos de POD.

Y luego lanzar el búfer de lectura directamente en el tipo de estructura:

std::vector<char> buf(sizeof(contents)); file.read(buf.data(), buf.size()); contents *stuff = reinterpret_cast<contents *>(buf.data());

Ahora, si el tamaño de sus datos es variable, puede separar en varias partes. Para leer un solo objeto binario desde el búfer, una función de lector es útil:

template<typename T> const char *read_object(const char *buffer, T& target) { target = *reinterpret_cast<const T*>(buffer); return buffer + sizeof(T); }

La principal ventaja es que este lector puede especializarse para objetos c ++ más avanzados:

template<typename CT> const char *read_object(const char *buffer, std::vector<CT>& target) { size_t size = target.size(); CT const *buf_start = reinterpret_cast<const CT*>(buffer); std::copy(buf_start, buf_start + size, target.begin()); return buffer + size * sizeof(CT); }

Y ahora en tu parser principal:

int n_floats; iter = read_object(iter, n_floats); std::vector<float> my_floats(n_floats); iter = read_object(iter, my_floats);

Nota: como observó Tony D, incluso si puede obtener la alineación correcta a través de las directivas #pragma y el relleno manual (si es necesario), es posible que aún encuentre incompatibilidad con la alineación de su procesador, en la forma de (mejor caso) problemas de rendimiento o (peor caso) trampas de señales. Este método probablemente sea interesante solo si tiene control sobre el formato del archivo.


Si no es para fines de aprendizaje, y si tiene libertad para elegir el formato binario, debería considerar usar algo como protobuf que se encargará de la serialización y le permitirá interactuar con otras plataformas e idiomas.

Si no puede utilizar una API de terceros, puede buscar en QDataStream como fuente de inspiración.


Tuve que resolver este problema una vez. Los archivos de datos fueron empaquetados de salida de FORTRAN. Las alineaciones estaban mal. Tuve éxito con los trucos del preprocesador que hicieron automáticamente lo que estás haciendo manualmente: desempaquetar los datos en bruto de un búfer de bytes a una estructura. La idea es describir los datos en un archivo de inclusión:

BEGIN_STRUCT(foo) UNSIGNED_SHORT(length) STRING_FIELD(length, label) UNSIGNED_INT(stride) FLOAT_ARRAY(3 * stride) END_STRUCT(foo)

Ahora puede definir estas macros para generar el código que necesita, digamos la declaración de la estructura, incluir lo anterior, undef y definir las macros nuevamente para generar funciones de desempaquetado, seguidas de otra inclusión, etc.

NB La primera vez que vi esta técnica utilizada en gcc para la generación de código relacionado con el árbol de sintaxis abstracta.

Si CPP no es lo suficientemente poderoso (o tal abuso del preprocesador no es para usted), sustituya un pequeño programa lex / yacc (o elija su herramienta favorita).

Me sorprende la frecuencia con la que vale la pena pensar en términos de generar código en lugar de escribirlo a mano, al menos en código de base de bajo nivel como este.



Yo personalmente lo hago de esta manera:

// some code which loads the file in memory #pragma pack(push, 1) struct someFile { int a, b, c; char d[0xEF]; }; #pragma pack(pop) someFile* f = (someFile*) (file_in_memory); int filePropertyA = f->a;

Manera muy efectiva para estructuras de tamaño fijo al inicio del archivo.