如何正确安全地使用sscanf函数

9
首先,关于使用sscanf的其他问题并不能回答我的问题,因为普遍的答案是根本不要使用sscanf而是使用fgetsgetch,但这在我的情况下是不可能的。
问题是我需要在程序中使用scanf,这是个要求。然而,程序还必须处理所有不正确的输入。
程序必须读取一个整数数组。数组中的整数以任何格式提供都无所谓。为了简化任务,程序可以先读取数组的大小,然后每次读取一个新行中的整数。
程序必须处理这些输入(并适当地报告错误):
1. 999999999999999...9(大于整数的数字) 2. 12a3(不要将其读取为整数12) 3. a...z(字符串) 4. 11 aa 22 33\n(所有内容在一行中,可以通过丢弃11后面的所有内容来处理此问题) 5. 输入比输入数组大
可能会有更多不正确的情况,这些是我能想到的唯一几种情况。
如果提供了错误的输入,程序必须要求用户重新输入,直到提供正确的输入,但是之前的正确输入必须保留(只有不正确的输入必须从输入流中清除)。
一切都必须符合C99标准。
5个回答

11
scanf函数家族在处理整数时不能安全使用,尤其是第一种情况特别麻烦。标准规定如下:

如果此对象没有适当的类型,或者转换的结果不能在对象中表示,则行为未定义。

简单明了。你可能会想到%5d等技巧,但你会发现它们不可靠。或者有人会想到errno。 scanf函数不需要设置errno
参考这个页面:最终放弃了使用scanf
因此,请回到您的C教授并询问他们:C99确切地要求sscanf如何报告错误?

1
从技术上讲,只要满足上述要求,就可以实现。将固定大小的输入读入字符串中,这是明确定义的,然后进行检查,并再次使用sscanf进行(现在可以证明有效的)转换。有点荒谬,但它满足了使用scanf而不破坏的要求; - davenpcj

2

让 sscanf 将所有输入均作为 %s(即字符串)接受,然后程序再进行分析。


2
scanf 中,简单的 %s 可能会导致缓冲区溢出。 - ThiefMaster
3
@ThiefMaster:是的,只要目标比源大,就不一定必须使用sscanf。另一方面,%s只会给你空格分隔的单词,而不是整个字符串。 - Keith Thompson
@ThiefMaster 使用类似于"%100s"的字段宽度(例如:限制100个字符),可用于避免溢出。 - davenpcj
@davenpcj:一个有100个前导零的数字(例如“000000000.....0001”)应该被正确处理吗? - Brendan
@Brendan:在OP的请求中,“handled correctly”意味着必须生成一个错误,或者如果要求允许这些数字(不清楚),则必须接受该值。为了使用此答案的方法而不发生缓冲区溢出,必须使用字段宽度限制。对于任何超过缓冲区大小的输入,必须将输入丢弃到下一个换行符。如果要求允许,可以检测并接受所有前导零(可选带有“-”符号)的输入。 - davenpcj

1
如果您必须使用scanf接受输入,我认为您可以从以下类似的代码开始。
int array[MAX];
int i, n;
scanf("%d", &n);
for (i = 0; i < n && !feof(stdin); i++) {
    scanf("%d", &array[i]);
}

这将(或多或少)处理自由格式输入问题,因为scanf在匹配%d格式时会自动跳过前导空格。

对于您的其他许多关注点的关键观察是,scanf告诉您它成功解析了多少个格式代码。所以,

int matches = scanf("%d", &array[i]);
if (matches == 0) {
   /* no integer in the input stream */
}

我认为这直接涉及到(3)和(4)的问题。

单独使用这个方法,无法完全处理输入12a3的情况。第一次循环时,scanf会将'12'解析为整数12,剩余的'a3'留给下一次循环。不过,下一次循环会出现错误。对于您的教授来说,这样做是否足够好呢?

对于大于maxint的整数,例如“999999.......999”,我不确定您能否直接使用scanf

对于大于输入数组的输入,这不是一个scanf问题。您只需要计算到目前为止已解析了多少个整数即可。

如果您被允许在从输入流中提取字符串后使用sscanf解码字符串,例如scanf("%s"),您也可以尝试以下方法:

while (...) {
    scanf("%s", buf);
    /* use strtol or sscanf if you really have to */
}

这适用于任何由空格分隔的单词序列,并且可以让您将扫描输入以查找单词与检查这些单词是否看起来像数字分开。如果必须,您可以针对每个部分使用scanf变体。

使用scanf('%c', c)代替getchar()怎么样?这样做既安全又满足要求,不是吗? - evodevo
真的,但是scanf(“%c”,&c)是一种昂贵的模拟getchar()的方法。 这个任务真的是关于这种琐碎的语法使用scanf吗? 我认为@cnicutar的评论真正抓住了一个核心问题:鉴于标准的引用摘录,使用scanf安全地解析溢出的整数输入是不可能的。 - Dale Hagglund
@evodevo:scanf('%c',c)是一个很好的例子,说明为什么在C++中通常应该避免使用格式字符串。太糟糕了,我们不能将评论加入书签:D - Sebastian Mach

0
问题是我的C语言教授要求我在程序中使用scanf。这是一个要求。然而,该程序还必须处理所有不正确的输入。
这是一个旧问题,所以提问者已经不在那位教授的课堂上了(希望教授已经退休了),但是为了记录,这是一个根本性错误和基本上不可能实现的要求。
经验表明,当涉及交互式用户输入时,scanf仅适用于快速且脏的情况,即“可以假定输入是正确的”。
如果您想快速轻松地读取整数(或浮点数或简单字符串),那么scanf是完成此任务的好工具。然而,它优雅地处理不正确的输入的能力基本上不存在。
如果您想稳健地读取输入,可靠地检测不正确的输入,并可能警告用户并要求他们重试,则scanf根本不是正确的工具。这就像试图用锤子拧螺丝钉一样。

查看此答案以获取在快速且简单的情况下安全使用scanf的一些指南。查看此问题以获取有关如何使用其他方法进行强大输入的建议,而不是使用scanf


-1

将scanf("%s", string)转换为long int_string = strtol(string, &end_pointer, base:10)


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