2

我正在研究一种应该在几种不同的操作系统和计算机中写入和读取的文件格式。其中一些计算机应该是 x86 机器,其他计算机应该是 x86-64。可能存在其他一些处理器,但我还不关心它们

此文件格式应包含几个数字,读取方式如下:

struct LongAsChars{
    char c1, c2, c3, c4;
};

long readLong(FILE* file){
    int b1 = fgetc(file);
    int b2 = fgetc(file);
    int b3 = fgetc(file);
    int b4 = fgetc(file);
    if(b1<0||b2<0||b3<0||b4<0){
        //throwError
    }

    LongAsChars lng;
    lng.c1 = (char) b1;
    lng.c2 = (char) b2;
    lng.c3 = (char) b3;
    lng.c4 = (char) b4;

    long* value = (long*) &lng;

    return *value;
}

并写成:

void writeLong(long x, FILE* f){
    long* xptr = &x;
    LongAsChars* lng = (LongAsChars*) xptr;
    fputc(lng->c1, f);
    fputc(lng->c2, f);
    fputc(lng->c3, f);
    fputc(lng->c4, f);
}

尽管这似乎可以在我的计算机上运行,​​但我担心它可能不会在其他计算机上运行,​​或者文件格式最终可能会因计算机而异(例如,32 位与 64 位计算机)。难道我做错了什么?我应该如何实现我的代码以使用每个数字的恒定字节数?

我应该只使用 fread (这也可能使我的代码更快)吗?

4

6 回答 6

8

使用输入类型stdint.h来确保输入和输出的字节数相同。

然后你就只剩下处理字节顺序问题了,你的代码可能并没有真正处理。

使用别名 char* 对 long 进行序列化会在写入文件中为具有不同字节顺序的平台留下不同的字节顺序。

您应该像这样分解字节:

char c1 = (val >>  0) & 0xff;
char c2 = (val >>  8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;

然后重构然后使用类似的东西:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);
于 2009-07-09T19:24:04.180 回答
1

您可能还会遇到字节序问题。为什么不直接使用NetCDFHDF之类的东西来解决可能出现的任何可移植性问题?

于 2009-07-09T19:24:32.573 回答
1

与其使用带有字符的结构,不如考虑一种更数学的方法:

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

这对于您要完成的工作更加直接和清晰。它也可以在循环中实现以处理更大的数字。

于 2009-07-09T19:30:28.240 回答
1

您不想使用 long int。这在不同的平台上可能有不同的大小,因此对于独立于平台的格式来说是非首发。您必须决定需要在文件中存储的值范围。32 位可能是最简单的。

你说你还不担心其他平台。我认为这意味着您希望保留支持它们的可能性,在这种情况下,您应该定义文件格式的字节顺序。x86 是 little-endian,因此您可能认为这是最好的。但是如果有的话,big-endian 是“标准”交换顺序,因为它用于网络。

如果你选择大端(“网络字节顺序”):

// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);

{
    // write value
    uint32_t value = 23;
    const uint32_t networkOrderValue = htonl(value);
    fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}

{
    // read value
    uint32_t networkOrderValue;
    fread(&networkOrderValue, sizeof(uint32_t), 1, file);
    uint32_t value = ntohl(networkOrderValue);
}

实际上,您甚至不需要声明两个变量,将“值”替换为同一个变量中等效的网络顺序会有点混乱。

它之所以有效,是因为“网络字节顺序”被定义为在内存中产生可互换(大端)顺序的任何位排列。无需弄乱联合,因为 C 中的任何存储对象都可以视为 char 序列。不需要对字节顺序进行特殊处理,因为这就是 ntohl/htonl 的用途。

如果这太慢了,您可以开始考虑使用 SIMD 或其他方式进行极其优化的特定于平台的字节交换。或者使用little-endian,假设您的大多数平台都是little-endian,因此它们“平均”更快。在这种情况下,您需要编写或找到“host to little-endian”和“little-endian to host”函数,当然在 x86 上什么也不做。

于 2009-07-09T21:15:47.247 回答
0

我相信最跨架构的方法是使用 uintXX_t 类型,如 stdint.h 中定义的那样。请参阅此处的手册页。例如 int32_t 将在 x86 和 x86-64 上为您提供 32 位整数。我现在在所有代码中默认使用这些,并且没有遇到任何问题,因为它们在所有 *NIX 中都是相当标准的。

于 2009-07-10T02:28:43.660 回答
0

假设sizeof(uint32_t) == 4存在4!=24可能的字节顺序,其中 little-endian 和 big-endian 是最突出的例子,但也使用了其他的(例如 PDP-endian)。

以下是用于从流中读取和写入 32 位无符号整数的函数,注意任意字节顺序,该字节顺序由表示为字节序列的整数指定0,1,2,3endian.hendian.c

标头定义了这些原型

_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);

和这些常数

LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER
于 2009-07-10T15:23:38.590 回答