使用 scanf 检测整数溢出

Detecting integral overflow with scanf

最近时,我发现代码有问题:

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

使用 strtol,您可以检测到溢出,因为在这种情况下,允许的最大值被插入 n 并且 errno 被设置为指示溢出,根据 C11 7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions /8:

If the correct value is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

但是,在处理 scanf 的标准部分中,特别是 C11 7.21.6.2 The fscanf function /10,我们看到:

If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

现在,对我来说,这意味着 任何 值都可以 returned 并且没有提到 errno 被设置为任何值。这是因为上面链接问题的提问者将 9,999,999,999 输入 32 位 int 并返回 1,410,065,407,值 233 太小,表示它只是在类型的极限处环绕。

试了一下,我得到了2,147,483,647,最大可能的32位无符号值。

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

现在我应该提一下,在我的系统 (Debian 7) 上,errno 在这种情况下实际上设置为 ERANGE 但我可以找到标准 中没有任何内容强制执行此操作。此外,scanf 的 return 值为 1,表示扫描项目成功。

唯一可移植的方法是指定字段宽度,例如使用 "%4d"(保证甚至适合 16 位 int)或通过在 运行 时构建格式字符串,字段宽度为 (int)(log(INT_MAX) / log(10))。这当然也拒绝了例如 32000,尽管它适合 16 位 int。所以不,没有令人满意的便携方式。

POSIX这里不多说,也不提ERANGE

This manpage mentions setting errno only in case EOF is returned; the glibc documentation 根本没有提到 ERANGE

这就留下了一个问题,那就是应该向初学者推荐什么来阅读整数,而我对此一无所知。 scanf 有太多未定义和未指定的方面,因此真正有用,fgets 不能用于生产代码,因为您无法正确处理 0 字节,并且使用 strtol 和朋友进行可移植错误检查比自己实现功能更多的行(而且很容易出错)。 atoi 的行为对于整数溢出也是未定义的。