如果有人抱怨gets()函数,为什么不对scanf("%s",...)也做同样的事情呢?

6

来自man gets

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

几乎到处都可以看到scanf以应该存在同样问题的方式使用:scanf("%s",string)。(缓冲区溢出/越界)这种问题在这种情况下存在吗?为什么scanf手册中没有相关引用?为什么gcc在使用-Wall编译时不发出警告?

附注:我知道有一种方法可以在格式字符串中指定字符串的最大长度:scanf

char str[10];
scanf("%9s",str);

编辑:我不是在问前面的代码是否正确。我的问题是:如果scanf("%s",string)总是错误的,为什么没有警告,并且手册中也没有任何关于它的内容?


1
你的维基百科链接说scanf不安全的 - aviraldg
@aviraldg 你说得对,我之前也读过这篇文章,但我一直没有找到一个好的标题写法。我已经修改了它。 - dbarbosa
1
正如其他人所回答的那样:scanf("%s",...)是不安全的。这里有一个关于此问题的参考链接:http://c-faq.com/stdio/scanfprobs.html。我仍然不明白为什么手册中没有任何关于这个问题的说明。 - dbarbosa
5个回答

6
答案很简单,就是没有人在GCC中编写代码来产生该警告。
正如您所指出的,对于"%s"(没有字段宽度)的特定情况,发出警告是非常恰当的。
但是,请记住,这只适用于scanf()vscanf()fscanf()vfscanf()的情况。这个格式说明符在sscanf()vsscanf()中可能是完全安全的,因此在这种情况下不应发出警告。这意味着您不能简单地将其添加到现有的“scanf-style-format-string”分析代码中;您需要将其拆分为“fscanf-style-format-string”和“sscanf-style-format-string”选项。
我相信,如果您为最新版本的GCC提供补丁,它有很大的机会被接受(当然,您还需要提交glibc头文件的补丁)。

你为什么说使用 sscanf()vsscanf() 可以完全安全?是因为你可以检查 scanf 读取的原始字符串(第一个参数)并确保它适合吗?例如,如果两个大小相同,如果查找空格位置等。 - dbarbosa
是的,没错。只要你控制输入,就可以安全地使用它。 - caf

4

使用 gets() 永远不安全。正如您在问题中所说的那样,scanf() 可以安全地使用。然而,确定是否安全使用它是编译器更难解决的问题(例如,如果您在一个函数中调用 scanf(),并将缓冲区和字符计数作为参数传递,编译器将无法告诉您是否安全);在这种情况下,它必须假设您知道自己在做什么。


是的,你说得对,但是当参数的数量或类型不正确时,gcc会发出警告。在这些情况下,它不会假设你知道自己在做什么。 - dbarbosa
@dbarbosa:编译器可以使用所有信息来检查可选参数的数量是否与格式字符串中的格式说明符相同。同样,它也有足够的信息来检查如果格式字符串中有“%d”(例如),则相应的参数是否为整数。 - Niall C.
它无法确定指定的大小是否正确,但是当没有任何大小规格时,它可以知道它是错误的。scanf("%5s",string) 取决于 string 的大小而可能正确或错误,正如你所说的那样,它无法判断。然而,由于缓冲区溢出问题,scanf("%s",string) 总是错误的。 - dbarbosa
@dbarbosa:你关于只使用“%s”作为格式说明符是正确的,但我还没有找到任何组合的gcc选项(我正在使用4.4.4)会发出警告。 - Niall C.

3

当编译器查看scanf的格式字符串时,它看到的是一个字符串!这是假设格式字符串不是在运行时输入的。一些编译器(如GCC)具有一些额外的功能来分析在编译时输入的格式字符串。那个额外的功能并不全面,因为在某些情况下需要运行时开销,而对于像C语言这样的语言来说是不可接受的。例如,在这种情况下是否可以检测出不安全的使用方式而不插入任何额外的隐藏代码:

char* str;
size_t size;
scanf("%z", &size);
str = malloc(size);
scanf("%9s"); // how can the compiler determine if this is a safe call?!

当然,如果你指定要读取的字符数,并且有足够的内存来存储字符串,那么使用 scanf 写安全代码是可行的。而在使用 gets 时,无法指定要读取的字符数。


1
确定这是否是一个安全的调用非常困难,但很容易看到scanf("%s", str)并警告用户,甚至比对传递给scanf的数字和参数类型进行检查更容易。 (顺便说一下,gcc会因为格式中没有%9s的参数而显示“警告:格式的参数太少”)。 - dbarbosa

1

我不确定为什么scanf的man页面没有提到缓冲区溢出的概率,但普通的scanf不是一个安全的选项。一个相当过时的链接 - Link 显示了这一点。此外,请查看这个(不是gcc,但仍然很有用)- Link


-4
可能只是因为 scanf 将根据读取的数据量在堆上分配空间。由于它不会分配缓冲区并一直读取,直到读取 null 字符,因此不会冒着覆盖缓冲区的风险。相反,它会读入自己的缓冲区,直到找到 null 字符,并预计将该缓冲区复制到读取末尾的另一个正确大小的缓冲区中。

3
不行,scanf函数不会在堆上分配空间,程序员需要提供缓冲区。正如dbarbosa所说,使用scanf存在不安全的风险。 - David Thornley
看一下这个教程,它更详细、更精确地解释了我刚才说的内容。http://crasseux.com/books/ctutorial/String-overflows-with-scanf.html - Graham
scanf 不会分配自己的存储空间。你链接的 a 标志是 GNU 扩展,不是 C 标准的一部分。 - John Ledbetter
1
你链接的教程中提到,scanf() 分配内存的标志是 Gnu 特有的扩展。在标准 C 中,scanf() 不会分配存储空间。 - David Thornley
需要注意的是,这个GNU扩展不仅基本上没用(因为你的代码将是非可移植的,在缺乏它的系统上无法在应用程序级别添加“a”功能),而且它直接与C99功能冲突——%a表示十六进制浮点数,就像printf一样,并且是使用scanf读取浮点数的替代说明符。 - R.. GitHub STOP HELPING ICE

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