【问题标题】:Writing and reading long int value in C code在 C 代码中写入和读取 long int 值
【发布时间】:2009-07-09 19:19:52
【问题描述】:

我正在研究一种应该在几种不同的操作系统和计算机中读写的文件格式。其中一些计算机应该是 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(这也可能使我的代码更快)吗?

【问题讨论】:

    标签: c io long-integer


    【解决方案1】:

    使用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);
    

    【讨论】:

    • @GMan - 你对联合没有同样的问题吗(除非你根据平台的字节序有条件地编译不同的联合定义)?
    • 对 stdint 的引用非常有用,会很有帮助!
    • 使用无符号字符或符号扩展会咬你。
    • @George - 你确定吗?但是,既然您提到它,我认为如果 sizeof(int)
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      这更直接、更清楚地说明了您要完成的工作。它也可以在循环中实现以处理更大的数字。

      【讨论】:

      • 这会以大端格式读取文件。这可能是一件好事,但在内存中读取整个 long 然后 bswap 它仍然会更快。
      • @ephemient: 假设你需要 bswap (如果你是 big-endian 怎么办?)。还假设 bswap 有效(如果你的 long 是 64 位呢?或者你在一些被遗弃的中间端机器上?)
      • 好吧,我在想“bswap 如果有必要”,但这显然不是我写的,我尽量不去考虑中端机器(它们在过去的二十年中存在吗? ) s/bswap/ntohl/ 呢?据我所知,如果给定 64 位值,它的常见实现会丢弃高 32 位,这是正确的做法。
      【解决方案4】:

      您不想使用 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 上当然什么也不做。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

          这里是用于从流中读取和写入 32 位无符号整数的函数,注意任意字节顺序,该字节顺序由表示为字节序列 0,1,2,3 的整数指定:endian.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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-07-12
            • 2011-05-08
            • 1970-01-01
            • 2011-03-08
            • 1970-01-01
            相关资源
            最近更新 更多