用scanf检测积分溢出
在最近回答另一个问题时 ,我发现代码有问题,例如:
int n; scanf ("%d", &n);
使用strtol
,您可以检测溢出,因为在这种情况下,允许的最大值被插入到n
并且errno
被设置为指示溢出,根据C11 7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions /8
:
如果正确的值超出可表示值的范围,则返回LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,ULONG_MAX或ULLONG_MAX(根据值的返回类型和符号,如果有),并且宏ERANGE的值为存储在errno中。
但是,在标准处理scanf
,特别是C11 7.21.6.2 The fscanf function /10
,我们看到:
如果此对象没有适当的类型,或者无法在对象中表示转换结果,则行为未定义。
现在,对我而言,这意味着可以返回任何值,并且没有提到errno
被设置为任何东西。 这一点已经曝光,因为上面链接问题的提问者将9,999,999,999
输入到32位int
并返回1,410,065,407
,值2 33
太小,表明它只是在类型的极限处缠绕。
当我尝试它时,我得到了2,147,483,647
,这是最大可能的32位无符号值。
所以我的问题如下。 在使用scanf
系列函数时,如何以便携方式检测积分溢出? 它甚至可能吗?
现在我应该提一下,在我的系统(Debian 7)中,在这些情况下, errno
实际上设置为ERANGE
但我在标准中找不到任何要求。 此外, scanf
的返回值为1
,表示扫描项目成功。
唯一可移植的方法是指定字段宽度,例如使用"%4d"
(保证甚至适合16位int
)或通过在运行时构建格式字符串,字段宽度为(int)(log(INT_MAX) / log(10))
。 这当然也拒绝例如32000,尽管它适合16位int
。 所以不,没有令人满意的便携方式。
POSIX在这里没有指定更多,也没有提到ERANGE
。
该联机帮助页仅在EOF
返回时提及设置errno
; glibc文档根本没有提到ERANGE
。
这留下了一个问题,建议初学者阅读整数,我不知道。 scanf
有太多未定义和未指定的方面非常有用, fgets
不能用于生产代码,因为你无法正确处理0字节,而strtol
和朋友的便携式错误检查比自己实现function需要更多的代码(而且非常简单弄错了。) 对于整数溢出, atoi
的行为也是未定义的。