使用 fread() 读取基于文本的文件 - 最佳实践

Using fread() to read a text based file - best practices

考虑使用此代码来读取基于文本的文件。 K.N 在优秀的著作 C Programming: A Modern Approach 中简要介绍了这种 fread() 用法。国王。 还有其他读取基于文本的文件的方法,但这里我只关心 fread()

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

int main(void)
{
    // Declare file stream pointer.
    FILE *fp = fopen("Note.txt", "r");
    // fopen() call successful.
    if(fp != NULL)
    {
        // Navigate through to end of the file.
        fseek(fp, 0, SEEK_END);
        // Calculate the total bytes navigated.
        long filesize = ftell(fp);
        // Navigate to the beginning of the file so
        // it can be read.
        rewind(fp);
        // Declare array of char with appropriate size.
        char content[filesize + 1];
        // Set last char of array to contain NULL char.
        content[filesize] = '[=10=]';
        // Read the file content.
        fread(content, filesize, 1, fp);
        // Close file stream pointer.
        fclose(fp);
        // Print file content.
        printf("%s\n", content);
    }
    // fopen() call unsuccessful.
    else
    {
        printf("File could not be read.\n");
    }
    return 0;
}

我在使用此方法时遇到了一些问题。我的意见是,这不是执行 fread() 的安全方法,因为如果我们尝试读取一个非常大的字符串,可能会发生溢出。这个意见靠谱吗?

为了避免这个问题,我们可以使用缓冲区大小并继续读入该大小的字符数组。如果 filesize 小于 buffer size,那么我们只需按照上面代码中的描述执行一次 fread()。否则,我们将总文件大小除以缓冲区大小并得到一个结果,我们将使用其 int 部分作为迭代循环的总次数,每次我们将调用 fread(),附加读取缓冲区数组成一个更大的字符串。现在,对于我们将在循环后执行的最终 fread(),我们将必须准确读取 (filesize % buffersize) 个字节的数据到该大小的数组中,最后将该数组附加到更大的字符串中(哪个我们会事先用 filesize + 1 编辑 malloc)。我发现如果我们使用 buffersize 作为第二个参数对最后一个数据块执行 fread(),那么额外的大小为 (buffersize - chunksize) 的垃圾数据将被读入并且数据可能会损坏。我的假设是否正确?请解释我是否/如何忽略了某些内容。

此外,还有一个问题是非 ASCII 字符的大小可能不是 1 个字节。在那种情况下,我会假设正在读取适当的数量,但每个字节都被读取为单个字符,所以文本以某种方式被扭曲了? fread() 如何处理多字节字符的读取?

this is not a safe method of performing fread() since there might be an overflow if we try to read an extremely large string. Is this opinion valid?

fread() 不关心 strings空字符 终止数组)。它读取数据时就好像它是 unsigned char*1 的倍数一样,如果流在 binary[=93= 中打开,则不特别关心数据内容] 模式和一些数据处理(例如 end-of-line、byte-order-mark)在 text 模式下。

Are my assumptions here correct?

失败的假设:

  • 假设 ftell() return 值等于 fread() 字节的总和。 该假设在 text 模式下可能是错误的(因为 OP 打开了文件)并且 fseek() 到最后是技术 undefined behavior in 二进制模式.

  • 假设不检查 fread() 的 return 值是可以的。使用 fread() 的 return 值可以知道是否发生错误,end-of-file 以及读取了多少字节的倍数。

  • 假设不需要错误检查。 , ftell(), fread(), fseek() 而不是 rewind() 都应该进行错误检查。特别是,ftell() 在没有确定终点的 上很容易失败。

  • 假设没有读取 空字符。一个文本文件并不能通过全部读取并附加一个空字符而成为一个字符串。健壮的代码检测 and/or 处理嵌入的空字符。

  • Multi-byte:假设输入满足编码要求。示例:健壮的代码检测(并拒绝)无效的 UTF8 序列 - 可能是在读取整个文件之后。

  • 极端:假设文件 length <= LONG_MAX,最大值 return 来自 ftell()。文件可能更大。

but each byte is being read as a single char, so the text is distorted somehow? How is fread() handling reading of multi-byte chars?

fread() 在 multi-byte 边界上不起作用,仅在 unsigned char 的倍数上起作用。给定的 fread() 可能以 multi-byte 的一部分结束,下一个 fread() 将从中间 multi-byte.

继续

考虑 1 次单次传递而不是 2 次传递方法

// Pseudo code
total_read = 0      
Allocate buffer, say 4096

forever
  if buffer full
    double buffer_size (`realloc()`)
  u = unused portion of buffer 
  fread u bytes into unused portion of buffer
  total_read += number_just_read
  if (number_just_read < u) 
    quit loop

Resize buffer total_read (+ 1 if appending a '[=10=]')

或者考虑在处理数据之前读取整个文件的需要。我不知道更高级别的目标,但通常在数据到达时对其进行处理会减少对资源的影响并提高吞吐量。


高级

文本文件可能很简单ASCII only, 8-bit code page defined, one of various UTF encodings (byte-order-mark,等等。最后可能以也可能不以[=结尾30=]。超越简单 ASCII 的强大文本处理是 non-trivial.

ASCII 和 UTF-8 是最常见的。 IMO,处理其中一个或两个,并在任何不符合他们要求的地方出错。


*1 fread() 根据第三个参数读取多个字节,在 OP 的情况下为 1。

//                       v --- multiple of 1 byte
fread(content, filesize, 1, fp);