getline() vs. fgets(): Control memory allocation
要从文件中读取行,有getline()和fgets() POSIX函数(忽略可怕的gets())。通常,getline()比fgets()更受青睐,因为它会根据需要分配行缓冲区。
我的问题是:那不是很危险吗?如果有人出于偶然或出于恶意目的创建了一个100GB的文件而没有'\
'字节怎么办–不会使我的getline()调用分配大量的内存吗?
- 这不取决于实现吗?
-
当然。我担心我的系统可能随后陷入停滞状态。使用fgets()不可能。我想知道我是否误会了某些东西,或者是否知道getline()的这种风险。
-
@xing我在手册页中看到的唯一错误代码是EINVAL。
-
an insane amount of memory-100GB是疯狂的内存量吗?是100KB?是1PB?告诉某人20年后的事...。多少内存是疯狂的,什么不是? getline()是1990年代的功能。它的存在是为了"使生活更轻松",而不是"处理用户想要的所有疯狂案例"。编写具有最大限制的getline()实现并不难。
-
@KamilCuk是,否和是。在20年内,答案将是"否","不是"和"是"。构成疯狂的内存量的确切阈值肯定会在以后几年中增加,但是任何有限的内存系统都将存在根本的问题(存在将导致内存不足或抖动的行)。
-
@KamilCuk:无论目标计算机有多少内存,包含100GB记录但没有长度前缀的文件的概念似乎有点疯狂。
-
@supercat:实际上,我不会说这很疯狂。如果您期望使用那种类型的文件,似乎疯狂的不使用fread;如果您不使用先前分配的缓冲区,则不检查getline的错误返回。
-
@xing有趣。我的手册页来自3.53 linux手册页发行版。你是新的吗?
-
an insane amount of memory –在从Zuse II到HAL 9000的任何计算机上,其大小都比RAM大小大一个数量级。
-
@jamesqf,即使将先前分配的内存缓冲区传递给getline(),它也会使用realloc()无限扩展。
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()限制其可以保留的(虚拟)内存总量来减轻风险。这可能导致失败,而不是成功分配足够的内存来干扰系统的其余部分。
我认为最好的总体做法是首先编写不需要以全行(一次)输入的代码,但是这种方法有其自身的复杂性。
- setrlimit可能有效,但是有陷阱;它不能限制现代内核上的常驻内存(RSS),并且虚拟内存(VSZ)可以并且经常确实确实超过物理RAM的大小,然后才开始发生波动,因此进行限制可能会导致分配失败,不这样做。 (stackoverflow.com/questions/39755928/…)、(superuser.com/questions/618687/…)
-
对于setrlimit(),@ Ray特别建议限制进程允许的虚拟内存数量,而不是限制其RSS。选择什么限制的问题是切线的,没有解决。
-
fgets的公平点。我已删除该异议。但是即使限制虚拟内存,setrlimit问题仍然存在。 RSS实际上是我们为避免抖动而要限制的内容,而VSZ则是一个宽松的上限。例如,我有一个运行的gvim副本,它使用5 MB的实际内存,但是使用550 MB的VSZ。使用setrlimit将虚拟内存限制为实际的RAM大小将导致分配在需要之前就失败。我同意setrlimit在这里可能有用,但是该方法存在一些问题,OP必须先意识到这些问题,然后OP才有用。
-
@Ray,这很公平,但是我认为,如果程序要求将整个行立即加载到内存中完全有意义,那么该程序将VSZ限制设置为比避免该操作所需的值小得多是合理的ing不休。毕竟,OP确实假定他描述的输入格式不正确。如果程序需要处理大行,那么就要求将它们完整地保存在内存中。
这可能很危险,是的。不知道在其他计算机上如何工作,但是运行下面的代码会使我的计算机冻结,需要硬重置:
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函数在内部使用malloc和realloc并在失败时返回-1,因此结果与尝试调用malloc(100000000000)的结果相同。即,errno设置为ENOMEM,getline返回-1。
因此,无论您使用getline还是尝试对fgets和手动内存分配执行相同的操作以确保阅读完整的行,都会遇到相同的问题。
-
除fgets()和手动分配外,您可以选择在进程的整个内存用尽之前进行纾困。
-
实际上,此行为与仅调用malloc(100000000000)有很大不同,因为getline()首先会分配一个较小的大小,然后逐步重新分配到找到换行符,文件末尾或内存限制的位置。此过程将对系统造成巨大压力,甚至可能无法使用,而malloc(100000000000)可能会立即返回NULL而不会产生任何后果。
某些编码准则(例如MISRA C)可能会阻止您使用动态内存分配(例如getline())。这是有原因的,例如避免内存泄漏。
如果您知道所有可接受的行的最大大小,则可以使用fgets()而不是getline()来避免内存分配,因此可以消除一个潜在的内存泄漏点。
这实际上取决于您要如何处理过长的行。
具有适当大小的缓冲区的
fgets通常可以正常工作,并且您可以检测到它已"失败"-缓冲区末尾没有换行符。可以避免总是执行strlen()来确认缓冲区是否溢出,但这是一个不同的问题。
也许您的策略是简单地跳过无法处理的行,或者行的其余部分仅仅是您无论如何都会忽略的注释,在这种情况下,很容易将fgets放入循环中丢弃该行的其余部分,而没有分配罚款。
如果您确实想阅读整行内容,那么getline可能是您更好的策略。恶意用户将需要大量磁盘空间来导致您描述的不良行为,或者可能传递/ dev / random或类似的名称作为输入文件名。
同样,如果getline无法重新分配,它将以一种可以从中恢复的方式失败,尽管如果您将缓冲区重用于多次行读取,则您可能希望在执行一次之后重新释放它确实拥有的缓冲区。在尝试读取更多内容之前发生错误,因为它仍在分配中,并且可能增长到失败之前的最大值。
-
我现在决定将行长度限制设置为10,000,并使用fgets()。在我的用例中,限制行长度是完全可以的。我问了最初的问题,因为在fgets()的glibc文档中有关于fgets()缺陷的警告,调用者无法分辨输入数据是否包含NUL字节,建议使用getline()。但是,对于getline()(可能由于不受控制的内存分配而导致系统崩溃),没有警告。
-
嗯...如果您的字符串包含空字符,那么很多c字符串API都会失效,但是如果包含,则它不是文本文件,因此您根本不应该使用基于行的处理。
-
就注入空字节的恶意可能性而言,您的代码将看到缩短的行,并且可能(取决于检测方式)将下一行视为行的延续。我无法立即看到这比任何其他稍显晦涩的文本文件输入还要糟糕。
-
确切地。但是,glibc文档对fgets进行了说明:"不要使用它来读取用户编辑的文件,因为如果用户插入空字符,则您应该正确处理它或打印清晰的错误消息。我们建议使用getline而不是fgets。" –让我感到惊讶的是,他们非常关心这种麻烦,但显然并不关心getline无限分配内存的危险。
-
嗯...他们是否建议一种正确处理的方法,可能吗?我是strlen vs ftell增量不匹配? ftell是一个可以快速调用的方法,但这确实意味着您必须调用strlen实际上,实际上使用ftell并不是一个坏策略,但是您确实需要处理Windows / unix文本文件。
-
他们建议使用getline,以便可以扫描该行中的NUL字节。对于fgets,无论如何都需要调用strlen,它将在行的末尾插入一个NUL字节–这就是为什么在遇到NUL时,无法分辨该文件是在文件中还是由<x17插入的原因。 >。 ftell方法适用于可搜索的文件,但不适用于类似stdin之类的管道流。或者,可以在将行缓冲区传递给fgets之前用已知的字节填充行缓冲区。如果NUL之后的字节与已知字节序列不同,则NUL最有可能来自文件。
-
最后,如果您将成为这种偏执狂,则设置自己的固定缓冲区,将其完全读取,使用memchr扫描换行符,将未使用的部分memcpy再次读取并没有困难!也许我会在某个时候写出来。
getline()为您重新分配缓冲区,以减轻程序中的内存管理。
但是,实际上,这可能会导致分配大量内存。如果这是一个问题,那么您应该采取额外的步骤来使用不会隐式分配内存的函数。