【问题标题】:How to handle portability issues in a binary file format如何处理二进制文件格式的可移植性问题
【发布时间】:2015-09-11 05:21:51
【问题描述】:

我正在设计一种二进制文件格式来存储字符串[不终止 null 以节省空间]和二进制数据。

我。处理小/大端系统的最佳方法是什么? 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);
    }

【问题讨论】:

  • 我投票关闭,因为太宽泛了,所以 - 抱歉,尝试不同的网站!
  • @Olaf:我会投票保留:这是一个非常实用的现实问题,一直在出现。仅仅因为它没有一个简单的答案并不意味着它不值得考虑。 (话虽如此,但我不是一个普通人,所以如果大家一致认为有一些实际的、现实世界的编程问题是这个网站解决的,那么我就在没有立场争论。)
  • @SteveSummit:我确实同意这个问题实际上很有趣(请注意我的“对不起”)。但是,这对于 SO 来说是题外话。我真的希望 OP 找到另一个站点(不确定,是否有一个堆栈交换)。投票:嗯,这显然是我的意见。如果其他人的想法不同,它保持打开状态。我可以忍受。
  • "不终止 null 以节省空间"...真的吗?终止 NULL 字节是每个字符串的单个字节。您的替代方法是存储一个长度,它可以是一个字节,但这会将字符串限制为 256 个字节或更少。更有用的是,长度将是多个字节,这实际上比终止 NULL 使用更多空间...

标签: c linux file format


【解决方案1】:

我。在我看来,在读/写时(可能使用 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);
}

[附注正如@Olaf 在其中一个 cmets 中正确指出的那样,在生产代码中,您需要处理这些函数中的 EOF 和错误。为简单起见,我将其省略了。]

【讨论】:

  • 会在 mmap ed 缓冲区上使用指针算法实现 get16() 吗?
  • 看到了吗?你实际上确实投票了;-) 注意@user1450181:你绝对应该添加错误处理,捕捉EOF/error 的方向!还要注意:getc() 返回int。对于负值,强制转换为 unsigned 是实现定义的。
  • @Olaf:是的,这里肯定有很多关于有符号变量和无符号变量的微妙之处。 (也感谢关于 EOF 和错误处理的提醒。)但是虽然 getc 确实被声明为返回有符号整数,但它会返回的唯一负值是 EOF;它的所有正常字符返回都保证是正数。
  • @SteveSummit:这就是为什么我首先使用int,检查EOF,然后转换为unsigned char,并根据需要向上转换为另一个未签名的。不是很优雅,但那应该是安全的。 (但是,它不想创建更多的程序代码)。 (我很高兴没有被 stdlib 函数所束缚)。
  • @Olaf:实际上,这是我有时会选择 not 进行详尽的逐个调用错误检查的少数几个地方之一。只需拨打getc 2 或4 或8 次或多次,然后在最后检查feofferror
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 2016-02-25
  • 1970-01-01
相关资源
最近更新 更多