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
函数在内部使用 malloc
和 realloc
,如果它们失败则使用 returns -1,因此结果与您尝试调用 malloc(100000000000)
。即,errno
设置为 ENOMEM
和 getline
returns -1。
因此,无论您使用 getline
还是尝试使用 fgets
和手动内存分配来确保阅读整行,都会遇到同样的问题。
真的,这取决于您想如何处理太长的行。
fgets
具有适当大小的缓冲区通常可以工作,并且您可以检测到它具有 "failed" - 缓冲区末尾没有换行符。可以避免总是执行 strlen() 来确认缓冲区是否溢出,但这是一个不同的问题。
也许您的策略是简单地跳过无法处理的行,或者该行的其余部分只是您无论如何都会忽略的注释,在这种情况下,很容易将 fgets
在一个循环中丢弃行的其余部分而没有分配惩罚。
如果您无论如何都想阅读整行,那么 getline
可能是更适合您的策略。恶意用户需要大量磁盘 space 才能导致您描述的不良行为,或者可能传递 /dev/random 或类似的输入文件名。
同样,如果 getline
无法重新分配,它将以一种您可以从中恢复的方式失败,但如果您正在重复使用缓冲区进行多行读取,您可能需要释放缓冲区在尝试阅读更多内容之前确实发生了错误,因为它仍在分配并且可能在失败之前已经变得尽可能大。
一些编码准则(如 MISRA C)可能会阻止您使用动态内存分配(如 getline()
)。这是有原因的,例如避免内存泄漏。
如果您知道所有可接受行的最大大小,那么您可以使用 fgets()
而不是 getline()
来避免内存分配,从而消除一个潜在的内存泄漏点。
要从文件中读取行,有 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
函数在内部使用 malloc
和 realloc
,如果它们失败则使用 returns -1,因此结果与您尝试调用 malloc(100000000000)
。即,errno
设置为 ENOMEM
和 getline
returns -1。
因此,无论您使用 getline
还是尝试使用 fgets
和手动内存分配来确保阅读整行,都会遇到同样的问题。
真的,这取决于您想如何处理太长的行。
fgets
具有适当大小的缓冲区通常可以工作,并且您可以检测到它具有 "failed" - 缓冲区末尾没有换行符。可以避免总是执行 strlen() 来确认缓冲区是否溢出,但这是一个不同的问题。
也许您的策略是简单地跳过无法处理的行,或者该行的其余部分只是您无论如何都会忽略的注释,在这种情况下,很容易将 fgets
在一个循环中丢弃行的其余部分而没有分配惩罚。
如果您无论如何都想阅读整行,那么 getline
可能是更适合您的策略。恶意用户需要大量磁盘 space 才能导致您描述的不良行为,或者可能传递 /dev/random 或类似的输入文件名。
同样,如果 getline
无法重新分配,它将以一种您可以从中恢复的方式失败,但如果您正在重复使用缓冲区进行多行读取,您可能需要释放缓冲区在尝试阅读更多内容之前确实发生了错误,因为它仍在分配并且可能在失败之前已经变得尽可能大。
一些编码准则(如 MISRA C)可能会阻止您使用动态内存分配(如 getline()
)。这是有原因的,例如避免内存泄漏。
如果您知道所有可接受行的最大大小,那么您可以使用 fgets()
而不是 getline()
来避免内存分配,从而消除一个潜在的内存泄漏点。