scanf("%[^\n]s",a)与gets(a)的区别

11

我被告知当用户输入字符串时,不应使用scanf。大多数专家和StackOverflow上的用户都建议使用gets()。我从未在StackOverflow上询问过为什么不应该使用scanf来处理字符串。这不是实际问题,但非常感谢能回答这个问题。

现在进入实际问题。我遇到了这种类型的代码-

scanf("%[^\n]s",a); 

这段代码会读取用户输入的字符串,直到检测到换行符为止,并将空格也视为字符串中的一部分。

如果我使用

,会有什么问题吗?

scanf("%[^\n]s",a);

gets函数的替代方案是什么?

相比之下,gets函数是否比scanf函数更优化,因为gets函数专门用于处理字符串。 请告诉我这个问题的答案。

更新

这个链接帮助我更好地理解了它。


2
使用 gets 也不是一个很好的选择,因为存在缓冲区溢出的风险。请使用 fgets - Etienne de Martel
4
通过将stdin作为最后一个参数传递,您可以使用fgets从标准输入读取内容。与gets相比,使用fgets的优点在于您可以指定缓冲区的长度,从而防止fgets读取过多的数据。由于存在向后兼容性原因,未修复gets中的安全风险。 - Etienne de Martel
@niko - 任何f*函数(比如说,fscanf)都可以用于stdin文件句柄来模拟非f*版本(在这种情况下,fscanf(stdin, ...)scanf(...)完全等效)。 - Chris Lutz
@EtiennedeMartel 你的意思是如果给定的大小为20,那么当用户输入达到20时,它会自动退出,甚至不等待用户的新行字符吗?好的,让我试一下。 - niko
3
不,从stdin读取将始终等待换行符。但是,如果用户输入的字符数超过了fgets要求的数量,它们将作为FILE *结构的一部分存储在缓冲区中,您无法访问它们。对fgets的第二次调用,不会读取更多用户输入,而是返回该缓冲区中的更多数据,直到其为空。(fgetc和其他逐个字符函数也是这样做的。) - Chris Lutz
显示剩余6条评论
2个回答

8

gets(3) 是非常危险的,应该尽可能避免使用。我无法想象有什么用途是 gets(3) 不会成为一个安全漏洞。

scanf(3)%s 也很危险 -- 你必须使用 "字段宽度" 指示符来指示你已分配的缓冲区的大小。如果没有字段宽度,则此程序与 gets(3) 一样危险:

char name[64];
scanf("%63s", name);

GNU C库提供了对`%s`的修饰符`a`,它可以为您分配缓冲区。这种非便携式扩展可能更容易正确使用。请注意保留HTML标记。
   The GNU C library supports a nonstandard extension that
   causes the library to dynamically allocate a string of
   sufficient size for input strings for the %s and %a[range]
   conversion specifiers.  To make use of this feature, specify
   a as a length modifier (thus %as or %a[range]).  The caller
   must free(3) the returned string, as in the following
   example:

       char *p;
       int n;

       errno = 0;
       n = scanf("%a[a-z]", &p);
       if (n == 1) {
           printf("read: %s\n", p);
           free(p);
       } else if (errno != 0) {
           perror("scanf");
       } else {
           fprintf(stderr, "No matching characters\n"):
       }

   As shown in the above example, it is only necessary to call
   free(3) if the scanf() call successfully read a string.

2
你可以在 %[ 或者 %s 中使用长度修饰符。 %64[a-z] 读取最多 64 个小写字母字符,而 %64s 读取最多 64 个非空格字符。 - Adam Rosenfield
@Adam:这会教训我不要在空腹(因此分心 :))的情况下写作 - 我读了scanf(3)scanf(3posix),寻找一些迹象表明我是错误的...但是事实上是这样的:输入字符串在遇到空格或最大字段宽度时停止。非常感谢! - sarnold
2
char name[64]; scanf("%64s", name); 不应该是 scanf("%63s", name); 吗?这样可以为 NUL 字符留出位置。 - Nan Xiao
@NanXiao,哇。我简直不敢相信我犯了那个错误。非常好的发现! - sarnold
2
但是这个答案和问题有什么关系呢?问题是关于实现基于行的输入(而不是跳过空格)。%sa修饰符都与主题毫不相关。 - AnT stands with Russia

5
首先,你的格式字符串中的s是什么意思并不清楚。%[^\n]部分是一个独立的格式说明符。它不是%s格式的修饰符,就像你认为的那样。这意味着"%[^\n]s"格式字符串将被scanf解释为两个独立的格式说明符:%[^\n]后跟一个单独的s。这会指示scanf读取直到遇到\n(留下\n未读)的所有内容,然后要求下一个输入字符是s。这根本没有任何意义。没有输入能匹配这种自相矛盾的格式。
其次,显然意思是scanf("%[^\n]", a)。这与[已不可用]gets(或fgets)有些接近,但并不相同。scanf要求每个格式说明符至少匹配一个输入字符。如果无法为所请求的格式说明符匹配任何输入字符,则scanf将失败并中止。这意味着scanf("%[^\n]",a)不能读取空输入行,即包含\n字符的行。如果将这样的行输入上述scanf,它将返回0表示失败,并保持a不变。这与典型的基于行的输入函数的工作方式非常不同。
(这是%[]格式的一个相当令人惊讶和似乎不合逻辑的属性。就我个人而言,我更喜欢%[]能够匹配空序列并产生空字符串,但这不是标准scanf的工作方式。)
如果你想以逐行方式读取输入,fgets是你最好的选择。

1
这篇文章更新得不错。建议将您的替代方案添加到那两个弱函数调用中。(注意:使用scanf("%[^\n]",a)时,'\n'仍然留在stdin中。) - chux - Reinstate Monica

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