5

根据以下规范strtol

如果主题序列具有预期的形式并且 base 的值为 0,则从第一个数字开始的字符序列应解释为整数常量。如果主题序列具有预期的形式,并且碱基的值在 2 到 36 之间,则应将其用作转换的碱基,并为每个字母赋予其值,如上所示。如果主题序列以减号开头,则转换产生的值应取反。指向最终字符串的指针应存储在 endptr 指向的对象中,前提是 endptr 不是空指针。

手头的问题是,在否定之前,该值不在 的范围内long。例如,在 C89 中(整数常量不能采用 type long long),写入-2147483648可能是溢出;你必须写(-2147483647-1)或类似的。

由于使用“整数常量”的措辞可以解释为将 C 规则应用于整数常量的类型,这可能足以使我们免于此处未定义的行为,但同样的问题(没有这么简单的问题)将适用于strtoll.

编辑:最后,请注意,即使它确实溢出,也应该返回“正确”值。所以这个问题实际上只是关于errno在这种情况下是否可以或必须设置。

4

2 回答 2

6

虽然我今天不能指出标准中的特定措辞,但当我strtol在 1990 年代为 4BSD 写作时,我很确定这不应该设置errno并确保我不会设置。这是基于标准中的措辞,还是与某人的个人讨论,我不再记得了。

为了避免溢出,这意味着必须非常小心地进行计算。我做了它unsigned long并包含了这个评论(仍然在libc各种 BSD 的源代码中):

    /*
     * Compute the cutoff value between legal numbers and illegal
     * numbers.  That is the largest legal value, divided by the
     * base.  An input number that is greater than this value, if
     * followed by a legal input character, is too big.  One that
     * is equal to this value may be valid or not; the limit
     * between valid and invalid numbers is then based on the last
     * digit.  For instance, if the range for longs is
     * [-2147483648..2147483647] and the input base is 10,
     * cutoff will be set to 214748364 and cutlim to either
     * 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
     * a value > 214748364, or equal but the next digit is > 7 (or 8),
     * the number is too big, and we will return a range error.
     *
     * Set 'any' if any `digits' consumed; make it negative to indicate
     * overflow.
     */

我(在某种程度上仍然)对 C 库中的此操作与语言本身的语法之间的不对称感到恼火(其中负数是两个单独-的标记,后跟数字,因此写作-217483648意味着-(217483648)成为-(217483648U)当然是217483648U肯定的!(当然假设是 32 位int;有问题的值因其他位大小而异。)

于 2013-06-09T01:04:32.950 回答
1

在 32 位平台-2147483648上不是 c89 下的溢出,它是 LONG_MIN 并且 errno == 0。

直接引用标准

返回值

成功完成后,strtol() 返回转换后的值(如果有)。如果无法执行转换,则返回 0 并且可以将 errno 设置为 [EINVAL]。如果正确值超出可表示值的范围,则返回 LONG_MAX 或 LONG_MIN(根据值的符号),并将 errno 设置为 [ERANGE]。

经过测试,这似乎与以下测试一致:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int main(int argc, char *argv[]) {
long val = strtol(argv[1], NULL, 10);
fprintf(stderr, "long max: %ld, long min: %ld\n", LONG_MAX, LONG_MIN);
fprintf(stderr, "val: %ld, errno: %d\n", val, errno);
    perror(argv[1]);
return 0;
}

在 32 位 x86 系统上编译时使用:

$ gcc -std=c89 foo.c -o foo

产生以下输出:

$ ./foo -2147483648
long max: 2147483647, long min: -2147483648
val: -2147483648, errno: 0
-2147483648: Success

$ ./foo -2147483649             
long max: 2147483647, long min: -2147483648
val: -2147483648, errno: 34
-2147483649: Numerical result out of range
于 2013-06-08T20:27:17.170 回答