【问题标题】:Why is my simple C program displaying garbage to stdout?为什么我的简单 C 程序向标准输出显示垃圾?
【发布时间】:2010-09-20 14:06:46
【问题描述】:

考虑以下简单的 C 程序,它将文件读入缓冲区并将该缓冲区显示到控制台:

#include<stdio.h>

main()
{
  FILE *file;
    char *buffer;
    unsigned long fileLen;
    //Open file
    file = fopen("HelloWorld.txt", "rb");
    if (!file)
    {
        fprintf(stderr, "Unable to open file %s", "HelloWorld.txt");
        return;
    }
    //Get file length
    fseek(file, 0, SEEK_END);
    fileLen=ftell(file);
    fseek(file, 0, SEEK_SET);
    //Allocate memory
    buffer=(char *)malloc(fileLen+1);
    if (!buffer)
    {
        fprintf(stderr, "Memory error!");
        fclose(file);
        return;
    }
    //Read file contents into buffer
    fread(buffer, fileLen, 1, file);
    //Send buffer contents to stdout
    printf("%s\n",buffer);    
    fclose(file);
}

它将读取的文件仅包含:

世界你好!

输出是:

Hello World!²²²²▌▌▌▌▌▌▌↔☺

自从我在 C/C++ 中做任何重要的事情以来已经有一段时间了,但通常我会假设分配的缓冲区比必要的大,但事实并非如此。

fileLen 最终为 12,这是准确的。

我现在在想我一定只是显示错误的缓冲区,但我不确定我做错了什么。

谁能告诉我我做错了什么?

【问题讨论】:

    标签: c file-io


    【解决方案1】:

    您通过查找文件末尾然后使用ftell() 来确定文件大小的方法是错误的:

    • 如果是文本文件,在没有fopen() 调用的第二个参数中打开"b",那么ftell() 可能不会告诉您可以从文件中读取的字符数。例如,windows 使用两个字节作为行尾,但读取时却是一个char。事实上,以文本模式打开的流的ftell() 的返回值仅在调用fseek() 时有用,而不是确定文件大小。
    • 如果是二进制文件,在fopen()的第二个参数中用"b"打开,那么C标准有这样的说法:

      将文件位置指示器设置为文件结尾,与 fseek(file, 0, SEEK_END) 一样,对于二进制流(因为可能出现尾随空字符)或任何具有状态相关编码但不能确定结束的流具有未定义的行为处于初始移位状态。

    因此,您所做的不一定适用于标准 C。最好的选择是使用 fread() 来读取,如果您碰巧需要更多内存,请使用 realloc()。您的系统可能会提供mmap(),或者可能会保证将文件位置指示器设置为二进制流的文件结尾——但依赖这些是不可移植的。

    另请参阅此 C-FAQ:What's the difference between text and binary I/O?

    【讨论】:

      【解决方案2】:

      您可以使用calloc 而不是malloc 来分配已经初始化的内存。 calloc 需要额外的参数。它对于分配数组很有用; calloc 的第一个参数表示要为其分配内存的数组中元素的数量,第二个参数是每个元素的大小。由于char 的大小始终为1,我们可以将1 作为第二个参数传递:

       buffer = calloc (fileLen + 1, 1);
      

      在 C 中,不需要强制转换 malloccalloc 的返回值。以上将确保即使文件读取因任何原因提前结束,字符串也会以空值终止。 calloc 确实比 malloc 需要更长的时间,因为它必须先将您请求的所有内存清零,然后再将其提供给您。

      【讨论】:

        【解决方案3】:

        JesperE 在您的示例中关于 nul 终止问题是正确的,我只是补充一点,如果您正在处理文本文件,最好使用 fgets() 或类似的东西,因为这将正确处理跨不同平台的换行符序列并将始终为您终止字符串。如果您真的在处理二进制数据,那么您不想使用 printf() 来输出数据,因为 printf 函数需要字符串,并且数据中的 nul 字节会导致输出截断。

        【讨论】:

        • 感谢您的建议。这是一个二进制文件(它的一部分)。我现在只是在使用 printf() 来了解我的方位和“调试”。
        【解决方案4】:

        JesperE 的方法会奏效,但您可能想知道还有另一种处理方法。

        您始终可以打印已知长度的字符串,即使没有 NUL 终止符,只需将长度提供给 printf 作为字符串字段的精度:

        printf("%.*s\n", fileLen, buffer);
        

        这允许您在不修改缓冲区的情况下打印字符串。

        【讨论】:

          【解决方案5】:

          您需要 NUL 终止您的字符串。添加

          buffer[fileLen] = 0;
          

          在打印之前。

          【讨论】:

          • 我曾经尝试过这样做,但由于某种原因,我看起来像是在放屁,并且出于某种愚蠢的原因使用了 \n 而不是 \0。告诉过你我生锈了!
          • 谢谢!我会将您的答案标记为已接受的答案,但奇怪的是按钮不再存在... WTF
          • 哈!哇。你可以知道我问一个问题已经有多久了。现在一切就绪,谢谢。
          猜你喜欢
          • 2021-12-04
          • 1970-01-01
          • 2021-11-02
          • 2016-04-15
          • 1970-01-01
          • 1970-01-01
          • 2021-02-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多