【问题标题】:C binary read and write fileC二进制读写文件
【发布时间】:2019-04-20 01:29:10
【问题描述】:

我正在使用二进制文件来读取整数数组,那么每个偶数 x 应该变成 2 * x,每个奇数 x 应该变成 3 * x。当我这样做时,它总是读取第二个整数(即2)。有什么想法吗?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{    
FILE *f;

f = fopen("inputData.txt", "w+b");
int n = 5;
int i;
for (i = 1; i <= n; ++i) {
    fwrite(&i, sizeof(int), 1, f);
}
int x;
fseek(f, 0, SEEK_SET);
while (fread(&x, sizeof(int), 1, f) == 1) {
    printf("%d ", x);
    if (x % 2 == 0) {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = x * 2;
        fwrite(&x, sizeof(int), 1, f);
    } else {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = 3 * x;
        fwrite(&x, sizeof(int), 1, f);
    }
}

fclose(f);
}

【问题讨论】:

    标签: c windows file binaryfiles fseek


    【解决方案1】:

    好的,我不太明白发生了什么,但在这种情况下,当使用读/写文件时,您似乎不能信任 fseekSEEK_CUR(我正在运行 Windows,并且标准功能众所周知,它与可能是问题的 Linux 不同)

    编辑:Andrew's answer 证实了我的怀疑。我的解决方案符合标准推荐的内容。

    我为解决此问题所做的工作是自己管理文件位置并寻找该位置,而不是在调用 fseek 时隐式依赖当前文件位置。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
     FILE *f;
    
    f = fopen("inputData.txt", "w+b");
    if (!f) { perror("cannot create input"); exit(1); }
    
    int n = 5;
    int i;
    for (i = 1; i <= n; ++i) {
        fwrite(&i, sizeof(int), 1, f);
    }
    
    
    int x;
    int pos=0;
    fseek(f, 0, SEEK_SET);
    while (fread(&x, sizeof(int), 1, f) == 1) {
        if (fseek(f, pos, SEEK_SET)) {perror("cannot seek");exit(1);}
        pos += sizeof(int);
        printf("%d %lu\n", x, ftell(f));
        if (x % 2 == 0) {
            x = x * 2;
        } else {
            x = 3 * x;
        }
        if (fwrite(&x, sizeof(int), 1, f) != 1) {perror("cannot write");exit(1);}
        if (fseek(f, pos, SEEK_SET)) {perror("cannot seek");exit(1);}
    }
    
    fclose(f);
    }
    

    现在程序的输出是(带有当前偏移量)

    1 0
    2 4
    3 8
    4 12
    5 16
    

    二进制文件的内容现在是(正如在小端架构上所预期的那样):

    03 00 00 00 04 00 00 00 09 00 00 00 08 00 00 00 0F 00 00 00
    

    所以这是一种解决方法,但至少可以正常工作。

    【讨论】:

    • 我在这里有点破纪录:检查你的文件操作的返回值。
    • 是的,有issues with fseek on Windows,但仅用于阅读文本。出于好奇,wb++ 之后的b 有什么不同吗?
    • 是的,文本文件的 ftell/fseek 损坏,因为在读取时删除了回车符。当我第一次运行 OP 代码时,我不相信我所看到的。我已经成功地将 fseek 用于二进制文件,但它们不是读/写的。我认为读写之间的混合混淆了 fseek,可能是缓冲的东西......
    • @Schwern Windows 上的fseek() 没有“问题”。每7.21.9.4 The ftell function, paragraph 2 of the C standard:“对于文本流,其文件位置指示符包含未指定的信息,fseek 函数可使用该信息将流的文件位置指示符返回到 ftell 调用时的位置;两个这样的返回之间的区别值不一定是衡量写入或读取字符数的有意义的指标。”
    • (cont) 在文本流上使用 fseek() 的唯一严格合规方式是使用它返回从先前调用 ftell() 返回的位置。
    【解决方案2】:

    这段代码:

    while (fread(&x, sizeof(int), 1, f) == 1) {
        printf("%d ", x);
        if (x % 2 == 0) {
            fseek(f, -sizeof(int), SEEK_CUR);
            x = x * 2;
            fwrite(&x, sizeof(int), 1, f);
        } else {
            fseek(f, -sizeof(int), SEEK_CUR);
            x = 3 * x;
            fwrite(&x, sizeof(int), 1, f);
        }
    }
    

    违反7.21.5.3 The fopen function, paragraph 7 of the C Standard(我的粗体字)中的限制:

    当文件以更新模式(“+”作为上述模式参数值列表中的第二个或第三个字符)打开时,可以在关联的流上执行输入和输出。但是,如果没有对 fflush 函数或文件定位函数(fseek、fsetpos 或 rewind)的干预调用,则输出不应直接跟随输入,并且 输入不应在没有干预调用的情况下直接跟随输出一个文件定位函数,除非输入操作遇到文件尾。

    每次循环迭代以fwrite() 调用结束,下一次循环迭代以fread() 调用开始,“没有对文件定位函数的干预调用”。

    【讨论】:

      【解决方案3】:
      f = fopen("inputData.txt", "w+b");
      

      w 截断文件(如果存在)。 + 不会改变这种行为。

      这是一个简单的演示。

      $ cat test.c
      #include <stdio.h>
      
      int main() {
          FILE *f;
      
          f = fopen("inputData.txt", "w+b");
          if( f == NULL ) {
              perror("open failed");
          }
      
          fclose(f);
      }
      
      $ make
      cc -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic -c -o test.o test.c
      cc -fsanitize=address  test.o   -o test
      
      $ cat inputData.txt 
      abc
      $ ./test
      $ cat inputData.txt 
      

      改为使用r+b


      另外两个问题。

      sizeof(int) 返回一个无符号数,size_t。您的编译器可能会为您将它们转换为 signed long,但应该明确地这样做。

      fseek(f, -(long)sizeof(int), SEEK_CUR);
      

      您没有检查您的文件操作是否成功。在这种情况下,它们是,但它们会默默地失败并且难以调试。检查所有文件操作很重要。

      if( fseek(f, -(long)sizeof(int), SEEK_CUR) != 0 ) {
          perror("fseek failed");
      }
      

      【讨论】:

      • 这不是唯一的问题,-sizeof(int) 不能在没有转换的情况下使用。此时,最好关闭为 no mcve 或 duplicate
      • 我在“r+b”中更改后仍然有同样的错误。检查编辑
      • @MihaiMyhai Works For Me™ 请务必检查您的文件操作的所有的返回值。
      • 好吧,我使用了你给我的所有建议,但没有一个奏效。它总是打印 2,我不知道为什么。也许是 textEditor/ 编译器错误?
      • @Schwern 我所有文件操作的返回值都是2
      猜你喜欢
      • 2019-09-18
      • 2013-07-10
      • 2020-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-03
      • 2021-06-22
      • 2012-01-26
      相关资源
      最近更新 更多