为什么gets()函数无法工作?

13

我正在Unix中使用C编程,我正在使用gets读取键盘输入。然而,我总是收到这个警告并且程序停止运行:

warning: this program uses gets(), which is unsafe.

有人能告诉我为什么会发生这种情况吗?


这是一个不安全的函数;如果程序使用这样的函数而没有提供检查缓冲区大小的可能性,则可能会发生缓冲区溢出攻击。 - INS
如果我使用scanf()代替gets(),程序可以运行,但是我需要从键盘读取两个输入,而且在读取完第一个输入后程序就会停止。为什么?以下是代码:char user; char pass; printf("用户ID"); scanf("%s",user); printf("密码:"); scanf("%s",pass); - Peiska
1
@peiska:程序在第一个停止是因为它正在等待您输入第二个。您自己放置了第二个scanf。为什么您会感到惊讶,程序然后“停止”呢? - AnT stands with Russia
userpass需要声明为char数组,而不是单个字符。"%s"转换说明符期望相应的参数具有类型char *,并且该参数需要指向一个足够长的缓冲区,以容纳输入字符串加上0终止符(未装饰的"%s"转换说明符在这方面暴露了与gets()相同的安全漏洞,但这里没有足够的空间详细讨论)。将userpass的声明更改为char user[N]; char pass[M],其中N和M是您需要的两个数组的大小。 - John Bode
4个回答

17

gets是不安全的,因为你给它一个缓冲区,却没有告诉它这个缓冲区有多大。输入可能会写过缓冲区的末尾,导致程序出现非常严重的问题。使用fgets会好一些,因为你可以告诉它缓冲区有多大,像这样:

const int bufsize = 4096; /* Or a #define or whatever */
char buffer[bufsize];

fgets(buffer, bufsize, stdin);

...所以只要您提供正确的信息,它就不会在缓冲区末尾写入并破坏事物。

略有偏差,但:

您不必为缓冲区大小使用const int,但我强烈建议您不要仅在两个位置都放置文字数字,因为不可避免地您将稍后更改一个但不更改另一个。编译器可以提供帮助:

char buffer[4096];
fgets(buffer, (sizeof buffer / sizeof buffer[0]), stdin);

那个表达式在编译时被解析,而不是运行时。打这个表达式很痛苦,所以我过去在我的头文件中使用宏:

#define ARRAYCOUNT(a) (sizeof a / sizeof a[0])

......但我的纯C知识已经过时几年了,现在可能有更好的方法。


如果你正在处理一个char数组,就没有必要除以sizeof buffer[0],因为根据定义sizeof(char)==1,也就是说函数调用可以简化为fgets(buffer,sizeof buffer,stdin) - Christoph
@Christoph:您确定char在所有平台上都是一个字节,或者如果它是(比如)两个字节,那么fgets的缓冲区大小参数将被应用为字符而不是字节吗?我见过的大多数文档都说是字节。我已经有一段时间没有玩C语言了,我知道这些东西变得更加复杂了,所以我在代码中有点防御性。但是,是的,@OP,如果这些假设是安全的,那么您可以省略使用char数组进行计算(但是对于intlong等类似习惯用法的应用不行)。 - T.J. Crowder
@T.J. Crowder:C标准定义char为1字节。 - jamesdlin
@T.J. Crowder:请参阅C99第3.7.1节,其中指出C字符是单字节的,以及6.5.3.4 §3,明确提到sizeof(char)始终为1。 - Christoph
这是C语言,不是C++,所以请使用#define或enum。 - Nyan
@jamesdlin和@chrisotph:非常好。正如我所说,我已经有一段时间没有接触C语言了,不想因为假设情况没有改变而误导他人。 - T.J. Crowder

9

如前面的答案所提到的,使用fgets代替gets

但并不是说gets完全不能用,它只是非常非常不安全。我猜测你的代码中可能有一个错误,即使使用fgets也会出现这个问题,请把你的源代码发布出来。

编辑 根据您在评论中提供的更新信息,我有几个建议。

  • I recommend searching for a good C tutorial in your native language, Google is your friend here. As a book I would recommend The C Programming Language

  • If you have new information it is a good idea to edit them into your original post, especially if it is code, it will make it easier for people to understand what you mean.

  • You are trying to read a string, basically an array of characters, into a single character, that will of course fail. What you want to do is something like the following.

    char username[256];
    char password[256];
    scanf("%s%s", username, password);
    

    Feel free to comment/edit, I am very rusty even in basic C.

编辑2 正如jamesdlin所警告的那样,使用scanfgets一样危险。


1
你的 scanf 用法和 gets 一样糟糕。(也要避免使用 scanf:http://c-faq.com/stdio/scanfprobs.html) - jamesdlin
@jamesdlin 感谢你提醒,我不知道缓冲区溢出问题,尽管我之前遇到过scanf的其他问题。 - ponzao

6

man gets说:

永远不要使用gets()。因为在不知道数据大小的情况下,无法确定gets()将读取多少个字符,并且由于gets()会继续存储超出缓冲区末尾的字符,因此使用它非常危险。它已被用来破坏计算机安全。请改用fgets()。


2

gets() 是不安全的。它只接受一个参数,即指向 char 缓冲区的指针。请考虑需要将缓冲区大小设置为多大以及用户输入时可以在未按回车键的情况下输入多长时间。

基本上,使用 gets() 无法防止缓冲区溢出 - 应该使用 fgets()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接