用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的行为也是未定义的。