getline() 与 fgets():控制内存分配

getline() vs. fgets(): Control memory allocation

要从文件中读取行,有 getline()fgets() POSIX 函数(忽略可怕的 gets())。 getline() 优于 fgets() 是常识,因为它根据需要分配行缓冲区。

我的问题是:这不是很危险吗?如果有人意外或恶意创建了一个 100GB 的文件,其中没有 '\n' 字节怎么办 – 这不会让我的 getline() 调用分配大量内存吗?

My question is: Isn’t that dangerous? What if by accident or malicious intent someone creates a 100GB file with no '\n' byte in it – won’t that make my getline() call allocate an insane amount of memory?

是的,您所描述的是一个似是而非的风险。然而,

  • 如果程序需要一次将整行加载到内存中,那么允许 getline() 尝试这样做本质上并不比编写自己的代码使用 fgets() 来执行它更具风险;和
  • 如果您的程序存在此类漏洞,则可以通过使用 setrlimit() 限制它可以保留的(虚拟)内存总量来降低风险。这可用于导致它失败,而不是成功分配足够的内存来干扰系统的其余部分。

我认为最好的总体方法是首先编写不需要以整行为单位(一次全部)输入的代码,但这种方法有其自身的复杂性。

getline() 为您重新分配缓冲区以减轻程序中的内存管理。

但实际上,这可能会导致分配大量内存。如果这是一个问题,那么您应该采取额外的步骤来使用不隐式分配内存的函数。

这可能很危险,是的。不知道这在其他计算机上如何工作,但是 运行 下面的代码使我的计算机冻结到需要硬重置的程度:

/* DANGEROUS CODE */

#include <stdio.h>

int main(void)
{
    FILE *f;
    char *s;
    size_t n = 0;

    f = fopen("/dev/zero", "r");
    getline(&s, &n, f);

    return 0;
}

getline 函数在内部使用 mallocrealloc,如果它们失败则使用 returns -1,因此结果与您尝试调用 malloc(100000000000)。即,errno 设置为 ENOMEMgetline returns -1。

因此,无论您使用 getline 还是尝试使用 fgets 和手动内存分配来确保阅读整行,都会遇到同样的问题。

真的,这取决于您想如何处理太长的行。

fgets 具有适当大小的缓冲区通常可以工作,并且您可以检测到它具有 "failed" - 缓冲区末尾没有换行符。可以避免总是执行 strlen() 来确认缓冲区是否溢出,但这是一个不同的问题。

也许您的策略是简单地跳过无法处理的行,或者该行的其余部分只是您无论如何都会忽略的注释,在这种情况下,很容易将 fgets 在一个循环中丢弃行的其余部分而没有分配惩罚。

如果您无论如何都想阅读整行,那么 getline 可能是更适合您的策略。恶意用户需要大量磁盘 space 才能导致您描述的不良行为,或者可能传递 /dev/random 或类似的输入文件名。

同样,如果 getline 无法重新分配,它将以一种您可以从中恢复的方式失败,但如果您正在重复使用缓冲区进行多行读取,您可能需要释放缓冲区在尝试阅读更多内容之前确实发生了错误,因为它仍在分配并且可能在失败之前已经变得尽可能大。

一些编码准则(如 MISRA C)可能会阻止您使用动态内存分配(如 getline())。这是有原因的,例如避免内存泄漏。

如果您知道所有可接受行的最大大小,那么您可以使用 fgets() 而不是 getline() 来避免内存分配,从而消除一个潜在的内存泄漏点。