【问题标题】:Reading a file of arbitrary length in C在C中读取任意长度的文件
【发布时间】:2019-05-23 18:56:53
【问题描述】:

在 C 中读取任意长度文件的最惯用/最有效的方法是什么?

  1. 以字节为单位获取文件的文件大小并发出单个fread()
  2. 保持fread()ing 的缓冲区大小不变,直到获得 EOF
  3. 还有别的吗?

【问题讨论】:

  • 这取决于你需要对这些数据做什么......
  • 内存映射通常很好
  • 只是 mmap(2) 它。
  • @n.m.是什么让您认为 OP 的平台支持mmap 系统调用? :)
  • @n.m.一些不幸的用户不得不使用不符合 POSIX 的 Windows。我很高兴在我的生活中从未使用过 Windows,但这是一种特权。

标签: c file fread


【解决方案1】:

避免使用任何需要事先知道文件大小的技术。只剩下一种技术:一次读取文件一点,以方便大小的块为单位。

这就是您不想尝试提前找到文件大小的原因:

  1. 如果不是普通文件,可能没有办法分辨。例如,您可能直接从控制台读取数据,或者从以前的数据生成器获取管道输入。如果您的程序要求知道文件大小,那么这些有用的输入机制将不提供给您的用户,他们会抱怨或选择不同的工具。

  2. 即使您可以计算出文件大小,也无法在读取文件时阻止它发生变化。如果您不小心读取文件的方式,您可能会打开一个可能被对抗程序利用的漏洞。

    例如,如果您分配一个“正确”大小的缓冲区,然后读取直到您获得文件结束条件,您最终可能会覆盖随机内存。 (如果您使用像read() 这样的接口可能读取的数据少于请求的数据,则可能需要多次读取。)或者您可能会发现文件已被截断;如果不检查读取的数据量,最终可能会处理未初始化的内存,导致信息泄漏。

【讨论】:

  • 我认为第 2 点需要详细说明。如果操作正确,在收集文件之后但在读取文件之前使用单个 fread 调用更改文件大小本身不会使程序易受攻击。
  • @sergeya:如果您使用单个 fread,如果文件大小增加,您将不会执行缓冲区溢出,但您可能不会注意到输入缓冲区的末尾充满了垃圾文件被截断。这更难但并非不可能利用,具体取决于发生了什么。
  • 这就是我的意思。增加文件大小不会以任何方式影响程序,而减小文件大小将导致 fread 返回的字节数少于请求的字节数。所以我真的看不到这里有任何漏洞(除非程序不检查 fread 调用的返回值,但无论如何所有的赌注都是关闭的)。
  • @SergeyA:我同意第一个原因更重要,这就是我把它放在第一位的原因。无论如何,大多数漏洞确实来自诸如不检查返回值而不是不安全的接口本身;事实上,我看到的大多数代码都没有检查返回值,因为编码器已经说服自己知道将读取多少数据。如果您正确地执行并检查了所有内容,那么您将是安全的,但代码将与分块读取文件的更好选择一样冗长。
  • 同意。我不认为我们在这里有争论,我们几乎是一致的。附带说明一下,任何不检查要返回值的系统或库调用的返回值的人都应该清楚地了解自己。
【解决方案2】:

实际上,您通常不需要将整个文件内容保存在内存中。您通常会parse 文件(特别是如果它是文本文件),或者至少以较小的部分读取文件,因此您不需要完全在内存中。对于文本文件,逐行读取(可能在解析器中包含某些状态)通常就足够了(使用fgetsgetline)。

Files 存在(特别是在disksSSDs 上),因为它们通常比您的计算机内存“大”得多。实际上,文件已经被发明(50 多年前)能够处理比内存更大的数据。 Distributed file systems 也可以很大(甚至可以通过笔记本电脑远程访问,例如通过NFSCIFS 等...)

一些 file systems 能够存储 PB 级的数据(在超级计算机上),以及许多 TB 级的单个文件(比可用 RAM 大得多)。

您也可能会使用一些databases。这些通常具有 TB 的数据。另请参阅this 答案(关于sqlite 数据库的实际大小)。

如果你真的想使用 stdio 读取整个内存中的文件(但你应该避免这样做,因为你通常希望你的程序能够处理文件上的大量数据;所以读取内存中的整个文件是通常是设计错误),您确实可以循环fread(或fscanf,甚至fgetc)直到文件结束。请注意,feof 只有在 一些输入操作之后才有用。

在当前的笔记本电脑或台式电脑上,您可能更喜欢(为了提高效率)使用几兆字节的缓冲区,而且您当然可以处理数百千兆字节的大文件(比您的 RAM 大得多)。

在 POSIX 文件系统上,您可以使用例如 memory mapped IO mmap(2) - 但这可能不会比具有大缓冲区(几兆字节)的read(2) 快。您可以使用readahead(2)(特定于Linux)和posix_fadvise(2)(或madvise(2),如果使用mmap)通过提示您的操作系统kernel来调整性能。

如果你必须为 Microsoft Windows 编写代码,你可以研究它的 WinAPIfind 一些方法来做内存映射 IO。

在实践中,文件数据(尤其是最近访问过的)通常保留在page cache 中,这对性能至关重要。如果不是这种情况,您的硬件(磁盘、控制器……)将成为瓶颈,而您的程序将成为I/O bound(在这种情况下,任何软件技巧都无法显着提高性能)。

【讨论】:

  • mmap 本身不会比fread 快。使mmapfread 更快的唯一方法是拥有两个线程 - 一个执行实际解析,另一个执行数据获取。
  • 在 Linux 上,带有m 标志的fopen 正在执行mmap
猜你喜欢
  • 1970-01-01
  • 2013-09-28
  • 1970-01-01
  • 2023-03-04
  • 2012-02-29
  • 2020-11-08
  • 1970-01-01
  • 2014-12-19
  • 1970-01-01
相关资源
最近更新 更多