C中的gets()函数
我需要帮助了! 我认为使用gets()
函数是相当酷的,因为它就像scanf()
其中我可以用空白来获得input。 但是我在其中一个线程( 学生信息文件处理 )中读到它不好用,因为根据它们,这是一个创build缓冲区溢出的恶魔工具(我不明白)
如果我使用gets()
函数,我可以这样做。 input你的名字: Keanu Reeves
。
如果我使用scanf()
,我只能这样做。 input你的名字: Keanu
所以我听取了他们的build议,并用fgets()
replace了我所有的gets()
代码。 问题是现在我的一些代码不工作了…是否有除gets()
和fgets()
之外的任何函数可以读取整个行,并忽略空白。
这是创build缓冲区溢出的恶魔工具
因为gets
不需要长度参数,所以它不知道input缓冲区有多大。 如果你传递一个10个字符的缓冲区并且用户input了100个字符 – 那么你就明白了。
fgets
是一个更安全的select,因为它将缓冲区长度作为参数,所以你可以这样调用它:
fgets(str, 10, stdin);
最多可以读入9个字符。
问题是现在我的一些代码不再工作了
这可能是因为fgets
还会将最后一个换行符( \n
)存储在缓冲区中 – 如果您的代码不期待这个,您应该手动删除它:
int len = strlen(str); if (len > 0 && str[len-1] == '\n') str[len-1] = '\0';
正如其他回复指出的, gets()
不会检查缓冲区空间。 除了意外的溢出问题之外,恶意用户可以利用这个弱点来制造各种各样的破坏。
1988年发布的第一批广泛传播的蠕虫之一使用gets()
在整个互联网上传播。 下面是Peter Van Der Linden的Expert C Programming的一个有趣摘录,讨论它是如何工作的:
早期的Bug获取()Internet蠕虫
C中的问题不仅限于语言。 标准库中的一些例程具有不安全的语义。 1988年11月,这个蠕虫程序在互联网上的数千台机器上蠕动,这一点已经得到了很好的certificate。 当烟雾已经清除并且调查完成时,确定蠕虫已经传播的一种方式是通过finger
守护进程中的弱点,该守护进程接受networking上关于谁正在login的查询。 finger
守护进程in.fingerd
,使用标准的I / O例程gets()
。
gets()
的名义任务是从stream中读取一个string。 来电者告诉它在哪里放入传入的字符。 但是gets()
不检查缓冲区空间; 实际上,它不能检查缓冲区空间。 如果调用者提供了一个指向堆栈的指针,并且input比缓冲区空间多, gets()
将会愉快地覆盖堆栈。 finger
守护进程包含代码:
main(argc, argv) char *argv[]; { char line[512]; ... gets(line);
在这里, line
是在栈上自动分配的一个512字节的数组。 当用户向finger
守护进程提供更多的input时, gets()
例程将继续把它放在堆栈上。 大多数体系结构都很容易用一些更大的东西覆盖堆栈中间的现有条目,也会覆盖相邻的条目。 检查每个堆栈访问的大小和权限的成本在软件中将是禁止的。 知识渊博的犯罪人可以通过在参数string中存储正确的二进制模式来修改堆栈上的过程激活logging中的返回地址。 这将会把执行stream程转移回来,而不是回到它来自的地方,而是转移到一个特殊的指令序列(也仔细地放在堆栈上),调用execv()
来用shell来replace正在运行的映像。 Voilà,你现在正在远程计算机上的一个shell而不是finger
守护进程,你可以发出命令将病毒副本拖到另一台机器上。
具有讽刺意味的是, gets()
例程是一个过时的函数,它提供了与可移植I / O库的第一个版本的兼容性,并在十多年前被标准I / O取代。 手册页甚至强烈build议总是使用fgets()
。 fgets()
例程设置了读取字符数量的限制,所以它不会超过缓冲区的大小。 finger
守护进程取而代之的是一个双线修复:
gets(line);
由行:
if (fgets(line, sizeof(line), stdin) == NULL) exit(1);
这吞没了有限的input量,因此不能被运行该程序的人操纵以覆盖重要的位置。 但是,ANSI C标准没有从语言中删除gets()
。 因此,虽然这个特定的程序是安全的,但C标准库中的潜在缺陷并没有被删除。
你可以看看这个问题: gets()
安全替代方法 。 有很多有用的答案。
你应该更精确地说明为什么你的代码不能和fgets()
。 正如另一个问题的答案所解释的,你必须处理get gets()
省略的换行符。
要阅读使用scanf的所有单词,你可以这样做
例如:
printf("Enter name: "); scanf("%[^\n]s",name); //[^\n] is the trick
你可以使用scanf
来模拟gets
。 虽然不是很漂亮
#include <stdio.h> #define S_HELPER(X) # X #define STRINGIZE(X) S_HELPER(X) #define MAX_NAME_LEN 20 int flushinput(void) { int ch; while (((ch = getchar()) != EOF) && (ch != '\n')) /* void */; return ch; } int main(void) { char name[MAX_NAME_LEN + 1] = {0}; while (name[0] != '*') { printf("Enter a name (* to quit): "); fflush(stdout); scanf("%" STRINGIZE(MAX_NAME_LEN) "[^\n]", name); /* safe gets */ if (flushinput() == EOF) break; printf("Name: [%s]\n", name); puts(""); } return 0; }
用fgets
读取和用sscanf
parsing(如果需要的话)会更好。
编辑解释scanf电话和周围的代码。
scanf
的“%[”转换规范接受不包含空终止符的最大字段宽度。 所以用于保存input的数组必须比用scanf读取多一个字符。
要做到这一点只有一个常数我使用了STRINGIZEmacros。 有了这个macros,我可以使用一个#define'd常量作为数组大小(对于variables定义)作为string(对于说明符)。
还有一个方面值得一提: flushinput
。 如果使用gets
,所有数据都写入内存(即使缓冲区溢出)直到但不包括换行符。 为了模仿这一点, scanf
读取有限数量的字符,但不包括换行符,并且与gets
不同, 将换行符保留在input缓冲区中 。 所以需要删除换行符,这就是flushinput
function。
其余的代码主要是build立一个testing环境。
你可以用scanf()
读取多个字段,所以你可以这样做:
scanf("%s %s\n", first_name, last_name);
不过,我认为最好先阅读一个string,然后再自己分开,因为它们可能没有input名字,或first / middle / last。
你用fgets()
有什么问题?
gets()
的问题在于它返回的字符数与用户input的一样多 – 你作为调用者无法控制这个。 所以你可以分配80个字符,用户可以input100个字符,而最后的20个字符将被写入你已经分配的内存末尾,跺脚谁知道什么。