【问题标题】:Odd output when reading and printing from file in C从C中的文件读取和打印时输出奇数
【发布时间】:2023-03-19 05:35:01
【问题描述】:

我编写了一个程序,它将读取并输出其自己的源文件的内容。我的目的主要是学习如何使用 I/O 流和“文件”类型。我在 Linux Ubuntu 14.04 上以纯文本文档编写程序,并使用终端编译和运行程序。这是终端从编译到完成的内容:

joseph@ubuntu:~/Desktop$ gcc test.c
joseph@ubuntu:~/Desktop$ ./a.out

File Opened

#include<stdio.h>
#define fileLocation ("/home/joseph/Desktop/test.c")
#define MAXREAD 1000

int main(void)
{
    char fileContents[MAXREAD];
    int i;

    FILE *tf;
    tf = fopen(fileLocation, "r");

    printf("File Opened\n");
    for(i=0;fileContents[i] != EOF; i++)
    {
        fileContents[i] = fgetc(tf);
        printf("%c", fileContents[i]);
    }
    fclose(tf);
    printf("\nFile Closed\n");
    return 0;
}
************************************************************

File Closed

* 符号实际上是 Unicode(0+FFFD:替换字符),但我似乎无法输入。

我的问题是,为什么它不在最后一个右花括号处结束程序,而是打印一堆替换字符?

【问题讨论】:

  • 文件大小是否
  • 关闭,文件大小为381字节。
  • 这里不需要fileContents 缓冲区 - 你得到一个字符,你输出一个字符 - 不需要缓冲区,如果文件大于 1000 字节,它将被溢出。 char ch = fgetc(tf); printf( "%c", ch ) ; 就足够了。如果您确实使用缓冲区,则可以使用 fread() 一次读取所有内容。
  • @Clifford fgetc(tf) 返回 257 个不同的值。其中 256 个表示已读取 char 并需要打印。其中之一,EOF 表示文件结束/IO 错误。因此需要将结果保存在int 而不是char
  • “为什么它不在最后一个右花括号处结束程序,而是打印一堆替换字符?”仅由@WhozCraig 回答。

标签: c io stream


【解决方案1】:

循环中的顺序不正确。您应该在 存储和打印您的字符值之前检查 EOF。您还应该确保没有超出数组边界。

int main(void)
{
    char fileContents[MAXREAD];
    int i, c;

    FILE *tf = fopen(fileLocation, "r");
    if (tf == NULL)
    {
        perror(fileLocation);
        return EXIT_FAILURE;
    }

    printf("File Opened\n");
    for (i=0; i < MAXREAD && (c = fgetc(tf)) != EOF; ++i)
    {
        fileContents[i] = c;
        fputc(fileContents[i], stdout);
    }
    fclose(tf);
    printf("\nFile Closed\n");
    return 0;
}

您的代码版本包括在 char 中打印错误存储的 EOF(这本身就是另一个问题,但通过不首先存储它来避免)。但这还远远没有结束你的困境。您继续 for 循环的条件逻辑是错误的。事实上,由于您从未初始化fileContents[],它实际上调用了未定义的行为。在每次迭代中,您都在检查一个尚未编写或初始化的数组槽。继续阅读如何/为什么

为什么要继续打印

控制表达式fileContents[i] != EOF 在每次循环迭代之前进行评估。增量表达式i++ 在每次迭代之后 执行,但 控制条件的下一次评估之前执行。来自标准:

声明

for ( clause-1 ; expression-2 ; expression-3 ) statement

的行为如下: 表达式expression-2 是在每次执行循环体之前计算的控制表达式。每次执行循环体后,表达式 expression-3 被评估为 void 表达式。如果clause-1 是一个声明,它声明的任何标识符的范围是声明的其余部分和整个循环,包括其他两个表达式;它在控制表达式的第一次评估之前按照执行顺序到达。如果clause-1 是一个表达式,则在第一次计算控制表达式之前,它会被计算为一个空表达式。

说白了,你刚刚保存在fileContents[i] 中的EOF 永远不会被检查,因为i 在下一次评估之前会递增。从上面的描述中可以理解。这就是简单循环的原因:

for (i=0; i<N; ++i)
    dostuff;

退出,i &lt; Nfalse。除非在dostuff 中发生不可预见的修改,否则循环将以i = N 终止。

同样,评估是在 增量步骤之后完成的,在您的情况下也是如此:

for(i=0; fileContents[i] != EOF; i++)

控制表达式fileContents[i] != EOF 在每次进入循环体之前进行评估。增量表达式发生在循环体之后,但之前 发生在控制表达式的下一个求值之前。在循环体中,您将EOF 存储在当前值i 索引的槽中。然后主体完成,i 递增,然后你才检查一个你没有写任何东西的插槽(还)。这种情况一直持续到某个时候,如果你(不)幸运,你会在新更新的i 索引中发现一个EOF 等效值。因此你终止了(但很可能,你在那之前很久就崩溃了)。

【讨论】:

  • 我同意这些问题,但是除非数组溢出(UB),否则为什么要多个'*'?
  • @chux - 缓冲区从来都不是问题 - 它总是不必要的,get-a-char ... print-a-char 只需要一个字符。
  • @Clifford “get-a-char 只需要一个字符 ...”。假设EOF 是-1。代码应如何区分读取 charfgets()(返回 257 个不同的值),所有位设置为 (char) 255EOF
  • @WhozCraig 我现在看到'i 索引在 fileContents[i] != EOF" 之前增加,导致麻烦。所以对于 OP 它只是缓冲区中的随机垃圾,或者过去,终于停止了循环。
  • 显然有人不同意。欢迎进行合理的讨论来反驳上述观点,但我不会屏住呼吸。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多