'fread()'、'fwrite()'和'fseek()'的意外行为

3

我写了一个简单的C程序,它可以读取一个.txt文件,并将所有空格替换为连字符。但是,该程序进入了一个无限循环,结果是无尽的连字符数组。

这是输入文件:

a b c d e f

这是处理崩溃后的文件:

a----------------------------------------------------------------------------
----------------------------------------... (continues thousands of times)... 

我猜想出现意外行为的原因可能是由于 fread(), fwrite(), 和 fseek() 函数的使用不当,或者是我的误解。以下是我的代码:

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

#define MAXBUF 1024

int main(void) {

    char buf[MAXBUF];
    FILE *fp;
    char c;
    char hyph = '-';

    printf("Enter file name:\n");
    fgets(buf, MAXBUF, stdin);
    sscanf(buf, "%s\n", buf);   /* trick to replace '\n' with '\0' */

    if ((fp = fopen(buf, "r+")) == NULL) {
        perror("Error");
        return EXIT_FAILURE;
    }

    fread(&c, 1, 1, fp);

    while (c != EOF) {
        if (c == ' ') {
            fseek(fp, -1, SEEK_CUR); /* rewind file position indicator to the position of the ' ' */
            fwrite(&hyph, 1, 1, fp); /* write '-' instead */
        }
        fread(&c, 1, 1, fp); /* read next character */
    }

    fclose(fp);

    return EXIT_SUCCESS;
}

这里的问题是什么?

你使用的是什么操作系统?我编译并运行了你的程序,由于没有正确检查EOF(正如Joe的答案中所述),它进入了一个无限循环,但它并不会创建一个无限数量的连字符流。 - David M. Syzdek
3个回答

2

您有两个问题:

1) 您应该检查fread返回的项目数,比如您是否收到了1个。

2) 然后您应该检查feof(fp),而不是将读取的字符与EOF进行比较。这将告诉您,如果您的读取因为EOF或其他原因返回了更少/没有项目。


你说得没错,但是它并没有修复这个 bug。真正修复它的是我在 fwrite(&hyph, 1, 1, fp); 这一行后面添加了 fseek(fp, 1, SEEK_CUR); 这一行。看起来 fwrite() 并没有像我想象中那样推进文件位置指针。所以我手动进行了操作。现在它可以正常工作了。无论如何还是谢谢你。 - Ori Popowski
1
如果 fwrite 没有推进文件位置,那么就有问题了。根据 opengroup 页面:流的文件位置指示器(如果定义)将被成功写入的字节数所推进。 - Dave

2
您有一些问题...
请检查标准C库函数返回的类型以及该返回值的含义。标准C库将EOF定义为整数-1。由于全字符集共有256个字符,而类型char可以容纳0到255(256个不同值),因此必须将EOF设为整数。
除了以上所有废话之外... 您也在错误地检查EOF。
问题如下:
您应该检查fread的返回值。
if( fread(&c, 1, 1, fp) != 1 )
{
    // Handle the error
}

// `EOF` is the integer -1.  It will not fit in a char.  So, your while loop becomes endless unless you get a -1 in the data stream

// The "correct" way to do what you want to do is using the stdlib function feof(fp)
while( !feof( fp ) )
{
    if (c == ' ')
    {
        // You should check the value returned by fseek for errors
        fseek(fp, -1, SEEK_CUR); /* rewind file position indicator to the position of the ' ' */
        // You should check the value returned by fwrite for errors
        fwrite(&hyph, 1, 1, fp); /* write '-' instead */
    }

    if( fread(&c, 1, 1, fp) != 1 )
    {
        // Handle the error
    }
}

所有这些话...在现代系统上,逐个读取字符非常低效。调整您的代码,一次读取一个缓冲区,并一次性写出整个修改后的缓冲区。

@JimR 他应该写"-",这是一个 const char *,而不是'-',这只是一个 const char - David M. Syzdek
@jimR hyph 是一个 char,然而 &hyph 是一个 char *。因此,你不能用 '-' 替换 &hyphconst char * ptr = &hyph;const char * ptr = "-"; 是有效的语法。const char * ptr = '-'; 是无效的。 - David M. Syzdek
@DavidM.Syzdek:我不是在争论这个问题。在我加入之前,这已经是他的代码的一部分了。我没有纠正它,因为他用&hyph解决了这个问题,而且我认为我已经给他提供了足够的信息,不需要再提到这个问题。出于同样的原因,我真的犹豫是否要提及效率问题,但由于我认为这是一个更大的长期问题... - JimR

1
原因:
对于打开更新操作(包括 "+" 符号)的文件,允许输入输出操作,必须在写操作后进行流刷新 (fflush) 或重新定位 (fseek, fsetpos, rewind),然后才能进行读取操作。在读取操作之前,必须重新定位 (fseek, fsetpos, rewind) 流,以便在写入操作之后进行 (只要该操作未达到文件结尾)。
解决方案:
在 fwrite 行后添加 "fflush(fp);"。

谢谢,我没有注意到参考文档中的这个注释,我一直在多个平台(Linux、Mac、BSD、Windows)上广泛使用 r+b 模式进行 fwrite+fread 操作,并且一直都能正常工作(文件位置被我天真地假设为已更新),但有一次我从零开始写入内容并在 Windows 构建时代码失败了...花了我整整一天才找到这个小注释。使用显式的 fseekfread 之前可以再次正常工作。 - Ped7g

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接