Estoy diseñando un formato de archivo binario para almacenar cadenas [sin terminar nulo para ahorrar espacio] y datos binarios.
i. ¿Cuál es la mejor manera de lidiar con sistemas little/big endian? ia ¿Funcionaría la conversión de todo a orden de bytes de red y viceversa con ntohl()/htonl()?
ii. ¿Serán las estructuras empaquetadas del mismo tamaño en x86, x64 y brazo?
iii. ¿Hay alguna debilidad inherente con este enfoque?
struct __attribute__((packed)) Header { uint8_t magic; uint8_t flags; }; struct __attribute__((packed)) Record { uint64_t length; uint32_t crc; uint16_t year; uint8_t day; uint8_t month; uint8_t hour; uint8_t minute; uint8_t second; uint8_t type; };Código de prueba que estoy usando para desarrollar el formato:
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <limits.h> #include <strings.h> #include <stdint.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> struct __attribute__((packed)) Header { uint8_t magic; uint8_t flags; }; struct __attribute__((packed)) Record { uint64_t length; uint32_t crc; uint16_t year; uint8_t day; uint8_t month; uint8_t hour; uint8_t minute; uint8_t second; uint8_t type; }; int main(void) { int fd = open("test.dat", O_RDWR|O_APPEND|O_CREAT, 444); struct Header header = {1, 0}; write(fd, &header, sizeof(header)); char msg[] = {"BINARY"}; struct Record record = {strlen(msg), 0, 0, 0, 0, 0, 0, 0}; write(fd, &record, sizeof(record)); write(fd, msg, record.length); close(fd); fd = open("test.dat", O_RDWR|O_APPEND|O_CREAT, 444); read(fd, &header, sizeof(struct Header)); read(fd, &record, sizeof(struct Record)); int len = record.length; char c; while (len != 0) { read(fd, &c, 1); len--; printf("%c", c); } close(fd); }i. Definir el archivo para que esté en un orden y convertirlo hacia y desde un orden "interno", si es necesario, al leer/escribir (quizás con ntohl y similares) es, en mi opinión, el mejor enfoque.
ii. No confío en las estructuras empaquetadas. Podrían funcionar para este enfoque para esas plataformas, pero no hay garantías.
iii. Leer y escribir archivos binarios usando fread y fwrite en estructuras completas es (de nuevo en mi opinión) un enfoque inherentemente débil. Maximiza la probabilidad de que le molesten problemas de tamaño de palabra, problemas de relleno y alineación, y problemas de orden de bytes.
Lo que me gusta hacer es escribir pequeñas funciones como get16() y put32() que leen y escriben un byte a la vez y, por lo tanto, son intrínsecamente insensibles al tamaño de la palabra y las dificultades del orden de los bytes. Luego escribo funciones sencillas putHeader y getRecord (y similares) en términos de estas.
unsigned int get16(FILE *fp) { unsigned int r; r = getc(fp); r = (r << 8) | getc(fp); return r; } void put32(unsigned long int x, FILE *fp) { putc((int)((x >> 24) & 0xff), fp); putc((int)((x >> 16) & 0xff), fp); putc((int)((x >> 8) & 0xff), fp); putc((int)(x & 0xff), fp); }[PD Como @Olaf señala correctamente en uno de los comentarios, en el código de producción necesitaría el manejo de EOF y error en estas funciones. Los he dejado fuera por simplicidad de presentación.]