使用scanf检测整数溢出

14

最近在回答另一个问题时,我发现了代码存在的问题:

int n;
scanf ("%d", &n);

使用 strtol 函数可以检测溢出,因为在溢出时,最大允许值将被插入到 n 中,并且根据 C11 7.22.1.4 的规定将 errno 设置为指示溢出的值,如下所示:

  

如果正确的值超出了可表示值的范围,则返回LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,ULONG_MAX或ULLONG_MAX(根据值的返回类型和符号,如果有任何值),并且将ERANGE的宏值存储在 errno 中。

然而,在涉及 scanf 的标准部分中,特别是 C11 7.21.6.2 节,我们看到:

  

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

现在对我来说,这意味着可以返回 任何 值,并且没有提到设置 errno 的任何内容。这一点之所以引起注意,是因为上面链接的问题的提问者正在将 9,999,999,999 输入到 32 位 int 中,并得到了 1,410,065,407,一个值,比类型的极限小了 233,这表明它只是在类型的限制处绕回了。

当我尝试时,我得到了返回值为最大可能的 32 位无符号值 2,147,483,647

所以我的问题如下。当使用 scanf 函数系列时,如何以可移植的方式检测整数溢出?这是可能的吗?

现在我应该提到,在我的系统(Debian 7)中,在这些情况下实际上将 errno 设置为 ERANGE,但我在标准中找不到任何强制执行此操作的内容。另外,scanf 的返回值为 1,表示成功扫描该项。


我可以看出errno在这里不能很好地工作的一个原因:scanf可以有各种转换。errno适用于哪些转换?(如果设置errno等同于转换失败,那将是明确的,但显然情况并非如此,即使在strtol中也不是如此。) - M Oehm
你的系统是什么?你说“在我的系统上,errno…” - Iharob Al Asimi
@iharob:Debian 7,我会更新问题,但我不是真的在寻求一个特定于实现的东西,而是一个符合标准的“合法”方案。 - paxdiablo
你应该做的第一件事是检查扫描的返回值--它返回成功转换的数量。在你的例子中,如果 (scanf(..) != 1),那么就会出现错误。 - Anonymouse
2
深入研究这个问题的意义何在?stdio.h,因此scanf,一直只适用于非常基本的输入。虽然scanf可能对于课堂或简单的数字计算应用程序来说足够好,但您不能安全地依赖它进行更多的操作。如果您想保护用户免受输入错误数据、控制或函数序列的影响,您必须考虑编写自己的库并从输入缓冲区中获取数据。 - Costis Aivalis
显示剩余4条评论
1个回答

6
唯一可移植的方法是指定字段宽度,例如使用"%4d"(保证适合16位int)或者通过在运行时构建格式字符串并将字段宽度设置为(int)(log(INT_MAX)/log(10))。当然,这也会拒绝例如32000这样的数字,尽管它可以适合16位int。因此,没有令人满意的可移植方法。 POSIX在此处未指定更多内容,也未提及ERANGE这个man页提到只有在返回EOF的情况下才设置errnoglibc文档根本不提到ERANGE
这就引出了一个问题,即对于读取整数,我们应该向初学者推荐什么。我不知道。 scanf有太多未定义和未指定的方面,以至于无法真正有用,fgets不能在生产代码中使用,因为您无法正确处理0字节,并且使用strtol和朋友们进行便携式错误检查需要更多的代码行,而实现该功能自己则相当容易出错。 atoi的行为对于整数溢出也是未定义的。

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