关于C#:getline()与fgets():控制内存分配

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

要从文件中读取行,有getline()fgets() POSIX函数(忽略可怕的gets())。通常,getline()fgets()更受青睐,因为它会根据需要分配行缓冲区。

我的问题是:那不是很危险吗?如果有人出于偶然或出于恶意目的创建了一个100GB的文件而没有'\
'
字节怎么办–不会使我的getline()调用分配大量的内存吗?


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

是的,您所描述的是一个合理的风险。但是,

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 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并在失败时返回-1,因此结果与尝试调用malloc(100000000000)的结果相同。即,errno设置为ENOMEMgetline返回-1。

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


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

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


这实际上取决于您要如何处理过长的行。

具有适当大小的缓冲区的

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

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

如果您确实想阅读整行内容,那么getline可能是您更好的策略。恶意用户将需要大量磁盘空间来导致您描述的不良行为,或者可能传递/ dev / random或类似的名称作为输入文件名。

同样,如果getline无法重新分配,它将以一种可以从中恢复的方式失败,尽管如果您将缓冲区重用于多次行读取,则您可能希望在执行一次之后重新释放它确实拥有的缓冲区。在尝试读取更多内容之前发生错误,因为它仍在分配中,并且可能增长到失败之前的最大值。


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

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