如何处理二进制文件格式的可移植性问题
How to handle portability issues in a binary file format
我正在设计一种二进制文件格式来存储字符串[不终止 null 以保存 space] 和二进制数据。
我。处理 little/big 字节序系统的最佳方法是什么?
i.a 将所有内容转换为网络字节顺序并使用 ntohl()/htonl() 返回是否可行?
二。压缩结构在 x86、x64 和 arm 上的大小是否相同?
三。他们使用这种方法有任何固有的弱点吗?
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;
};
我正在使用开发格式的测试代码:
#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);
}
我。将文件定义为一个顺序并在 "internal" 顺序之间转换,如果有必要,在 reading/writing 时(可能使用 ntohl 等)在我看来是最好的方法。
二。我不信任打包结构。他们可能适用于这些平台的这种方法,但不能保证。
三。在整个结构上使用 fread 和 fwrite 读写二进制文件(在我看来)本质上是一种薄弱的方法。您最大限度地提高了遇到字长问题、填充和对齐问题以及字节顺序问题的可能性。
我喜欢编写像 get16() 和 put32() 这样的小函数,它们一次读取和写入一个字节,因此本质上对字长和字节顺序困难不敏感。然后我根据这些编写简单的 putHeader 和 getRecord 函数(以及类似函数)。
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);
}
[P.S。正如@Olaf 在其中一条评论中正确指出的那样,在生产代码中,您需要处理这些函数中的 EOF 和错误。为了简单起见,我将它们排除在外。]
我正在设计一种二进制文件格式来存储字符串[不终止 null 以保存 space] 和二进制数据。
我。处理 little/big 字节序系统的最佳方法是什么? i.a 将所有内容转换为网络字节顺序并使用 ntohl()/htonl() 返回是否可行?
二。压缩结构在 x86、x64 和 arm 上的大小是否相同?
三。他们使用这种方法有任何固有的弱点吗?
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;
};
我正在使用开发格式的测试代码:
#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);
}
我。将文件定义为一个顺序并在 "internal" 顺序之间转换,如果有必要,在 reading/writing 时(可能使用 ntohl 等)在我看来是最好的方法。
二。我不信任打包结构。他们可能适用于这些平台的这种方法,但不能保证。
三。在整个结构上使用 fread 和 fwrite 读写二进制文件(在我看来)本质上是一种薄弱的方法。您最大限度地提高了遇到字长问题、填充和对齐问题以及字节顺序问题的可能性。
我喜欢编写像 get16() 和 put32() 这样的小函数,它们一次读取和写入一个字节,因此本质上对字长和字节顺序困难不敏感。然后我根据这些编写简单的 putHeader 和 getRecord 函数(以及类似函数)。
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);
}
[P.S。正如@Olaf 在其中一条评论中正确指出的那样,在生产代码中,您需要处理这些函数中的 EOF 和错误。为了简单起见,我将它们排除在外。]