为什么gets函数非常危险,不应该使用?

当我尝试编译使用gets函数的C代码时,我得到一个警告: warning: the gets function is dangerous and should not be used.

我记得这与堆栈保护和安全有关,但我不确定为什么。 有人可以帮我解决这个警告,并解释为什么有这样的警告? 如果gets是如此“危险”,那么为什么我们不能删除它?

为了安全使用,您必须确切知道您将读取多less个字符,以便您可以使缓冲区足够大。 你只会知道,如果你确切地知道你会读什么数据。

而不是使用gets ,你想使用fgets ,它有签名

 char* fgets(char *string, int length, FILE * stream); 

fgets ,如果它读取一整行,将把'\n'留在string中,你必须处理这个。)

直到1999年的ISO C标准,它仍然是该语言的官方部分,但在2011年的标准中被正式删除。 大多数C实现仍然支持它,但至lessgcc会为使用它的代码发出警告。

为什么gets()危险的

第一个互联网蠕虫( Morris Internet Worm )在27年前(1988-11-02)逃脱了,它使用gets()和缓冲区溢出作为从系统传播到系统的一种方法。 基本的问题是函数不知道缓冲区有多大,所以它继续读取,直到find一个换行符或遇到EOF,并且可能溢出缓冲区的边界。

你应该忘记你听说过gets()存在。

取消了C11标准ISO / IEC 9899:2011,将其作为标准function,即A Good Thing™。 遗憾的是,由于向后兼容的原因,图书馆仍将留在图书馆多年(意思是“数十年”)。 如果这取决于我, gets()的实现将变为:

 char *gets(char *buffer) { assert(buffer != 0); abort(); return 0; } 

鉴于你的代码无论如何将会崩溃,迟早要把麻烦尽快解决好。 我会准备添加一条错误消息:

 fputs("obsolete and dangerous function gets() called\n", stderr); 

Linux编译系统的现代版本会在链接gets()产生警告 – 还有一些其他也存在安全问题的函数( mktemp() ,…)。

gets()替代方法

与fgets()

正如其他人所说, gets()的规范替代方法是fgets()指定stdin作为文件stream。

 char buffer[BUFSIZ]; while (fgets(buffer, sizeof(buffer), stdin) != 0) { ...process line of data... } 

没有人提到的是gets()不包括新行,但fgets() 。 所以,你可能需要使用fgets()的wrapper来删除换行符:

 char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp) { if (fgets(buffer, buflen, fp) != 0) { size_t len = strlen(buffer); if (len > 0 && buffer[len-1] == '\n') buffer[len-1] = '\0'; return buffer; } return 0; } 

另外,正如caf在评论中指出的那样, paxdiablo在他的回答中显示,用fgets()你可能将数据留在一行上。 我的封装代码留下了下次要读取的数据; 你可以随意修改它,如果你愿意,可以吞噬其余的数据行:

  if (len > 0 && buffer[len-1] == '\n') buffer[len-1] = '\0'; else { int ch; while ((ch = getc(fp)) != EOF && ch != '\n') ; } 

剩余的问题是如何报告三种不同的结果状态 – EOF或错误,行读取和不截断,部分行读取但数据被截断。

gets()不会产生这个问题,因为它不知道你的缓冲区在哪里结束,愉快地践踏,最终在你精美的内存布局上造成破坏,如果缓冲区经常搞乱返回堆栈( 堆栈溢出 )在缓冲区是dynamic分配的情况下,或者在缓冲区是静态分配的情况下,将数据复制到其他宝贵的全局(或模块)variables上时,在栈上分配,或者践踏控制信息。 这些都不是一个好主意 – 它们是“未定义的行为”的缩写。


还有TR 24731-1 (C标准委员会的技术报告),它为各种function提供了更安全的替代方法,包括gets()

gets_s函数

概要

 #define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n); 

运行约束

s不得为空指针。 n不能等于零,也不能大于RSIZE_MAX。 从stdin读取n-1字符时,将出现换行符,文件结束或读取错误。 25)

3如果存在运行时约束冲突,则将s[0]设置为空字符,并且从stdin中读取和丢弃字符,直到读取新行字符,或发生文件结束或读取错误。

描述

4 gets_s函数最多只读取一个比由stdin指向的stream所指定的字符数less一个字符的数据到s指向的数组中。 在换行符(丢弃)或文件结束后,不会读取其他字符。 丢弃的换行字符不计入读取的字符数。 在读入数组的最后一个字符之后立即写入一个空字符。

5如果遇到文件结束且没有字符读入数组,或者在操作过程中发生读取错误,则将s[0]设置为空字符,并且s的其他元素采用未指定的值。

build议的做法

6 fgets函数允许正确编写的程序安全地处理input行太长,无法存储在结果数组中。 一般来说,这要求fgets调用者在结果数组中注意是否存在换行符。 考虑使用fgets (以及任何基于换行符的处理)而不是gets_s

25)gets不同, gets_s函数使得它对于一行input来说是一个运行时限制冲突,以便溢出缓冲区来存储它。 与fgets不同, gets_s在input行和成功调用gets_s之间保持一对一的关系。 使用的程序gets这样的关系。

Microsoft Visual Studio编译器实现了TR 24731-1标准的近似值,但Microsoft和TR中的签名之间存在差异。

C11标准ISO / IEC 9899-2011包括附件K中的TR24731作为图书馆的可选部分。 不幸的是,它很less在类Unix系统上实现。


getline() – POSIX

POSIX 2008还提供了getline() 。 它dynamic地为线路分配空间,所以你最终需要释放它。 因此,它消除了对线路长度的限制。 它还返回读取的数据的长度,或-1 (而不是EOF !),这意味着input中的空字节可以可靠地处理。 还有一个叫做getdelim()的“select你自己的单字符分隔符”的变体。 例如,如果您正在处理来自find -print0的输出(例如,文件名的末尾用ASCII NUL '\0'字符标记),这可能很有用。

因为get从stdin获取字节并将它们放在某个地方时不做任何types的检查。 一个简单的例子:

 char array1[] = "12345"; char array2[] = "67890"; gets(array1); 

现在,首先你可以input你想要的字符数,不会在意的。 其次,超过你放置它们的数组的大小的字节(在这个例子中是array1 )会覆盖他们在内存中find的任何东西,因为gets会写入它们。 在前面的例子中,这意味着如果你input"abcdefghijklmnopqrts"也许不可预知,它也会覆盖array2或者其他东西。

该function是不安全的,因为它假定input一致。 永远不要使用它!

您不应该使用gets因为它无法停止缓冲区溢出。 如果用户input的数据超过缓冲区中的数据,那么最可能的情况就是损坏或者更糟。

事实上,ISO实际上已经采取了从C标准中取消 gets的步骤(从C11开始,虽然在C99中已经废弃了),考虑到向后兼容性的高度,它应该表明这个function有多糟糕。

正确的做法是将fgets函数与stdin文件句柄一起使用,因为您可以限制从用户读取的字符。

但是这也有它的问题,如:

  • 用户input的额外字符将在下一次被拾取。
  • 没有用户input太多数据的快速通知。

为此,他们职业生涯某个时候几乎所有的C编码器都会在fgets编写一个更有用的包装器。 这是我的:

 #include <stdio.h> #include <string.h> #define OK 0 #define NO_INPUT 1 #define TOO_LONG 2 static int getLine (char *prmpt, char *buff, size_t sz) { int ch, extra; // Get line with buffer overrun protection. if (prmpt != NULL) { printf ("%s", prmpt); fflush (stdout); } if (fgets (buff, sz, stdin) == NULL) return NO_INPUT; // If it was too long, there'll be no newline. In that case, we flush // to end of line so that excess doesn't affect the next call. if (buff[strlen(buff)-1] != '\n') { extra = 0; while (((ch = getchar()) != '\n') && (ch != EOF)) extra = 1; return (extra == 1) ? TOO_LONG : OK; } // Otherwise remove newline and give string back to caller. buff[strlen(buff)-1] = '\0'; return OK; } 

用一些testing代码:

 // Test program for getLine(). int main (void) { int rc; char buff[10]; rc = getLine ("Enter string> ", buff, sizeof(buff)); if (rc == NO_INPUT) { printf ("No input\n"); return 1; } if (rc == TOO_LONG) { printf ("Input too long\n"); return 1; } printf ("OK [%s]\n", buff); return 0; } 

它提供了与fgets相同的保护,它可以防止缓冲区溢出,但它也会通知调用者发生了什么,并清除多余的字符,以免影响您的下一个input操作。

随意使用它,如您所愿,我特此发布下“做你想做的事”许可证:-)

fgets 。

从标准input读取:

 char string[512]; fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */ 

您不能删除API函数而不会破坏API。 如果愿意,许多应用程序将不再编译或运行。

这是一个参考文献给出的原因:

读取由s指向的数组溢出的行会导致未定义的行为。 build议使用fgets()。

我最近读了一篇关于comp.lang.c的USENET文章 ,指出get gets()被从标准中移除。 WOOHOO

你会很高兴知道委员会刚刚投票(一致,结果是)从草案中删除()。

在C11(ISO / IEC 9899:201x)中, gets()已被删除。 (在ISO / IEC 9899:1999 / Cor.3:2007(E)中已弃用)

除了fgets() ,C11引入了一个新的安全替代方法gets_s()

C11 K.3.5.4.1 gets_s函数

 #define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n); 

但是,在“ 推荐实践”部分中, fgets()仍然是首选。

fgets函数允许正确编写的程序安全地处理input行太长而不能存储在结果数组中。 一般来说,这要求fgets调用者在结果数组中注意是否存在换行符。 考虑使用fgets (以及任何基于换行符的处理)而不是gets_s

我想向所有仍在包括图书馆的C图书馆维护人员发出诚挚的邀请,以防止任何人依然依赖它。请将您的实施replace为

 char *gets(char *str) { strcpy(str, "Never use gets!"); return str; } 

这将有助于确保没有人依靠它。 谢谢。

C获取function是危险的,一直是一个非常昂贵的错误。 托尼·霍尔(Tony Hoare)在他的演讲“空引用:十亿美元的错误”中特别提到:

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

整整一个小时都值得关注,但从30分钟的评论看,具体受到批评约39分钟。

希望这会激起你对整个谈话的兴趣,这引起了我们如何在语言中需要更多正式的正确性certificate,以及如何将语言devise者归咎于他们语言中的错误,而不是程序员。 这似乎是糟糕的语言devise者以“程序员自由”为幌子,责怪程序员的可疑原因。

gets()是危险的,因为用户可能通过在提示中input太多而使程序崩溃。 它无法检测到可用内存的末端,因此如果为此分配的内存量太小,可能会导致分段故障并崩溃。 有时,用户似乎不太可能将1000个字母input到用于名称的提示中,但作为程序员,我们需要使我们的程序具有防弹性。 (如果用户可以通过发送太多数据来使系统程序崩溃,那么这也可能是安全风险)。

fgets()允许你指定从标准input缓冲区中取出多less个字符,所以它们不会溢出variables。