检测strtol函数的失败情况

28

我该如何检测strtol()未转换数字的情况? 我在以下简单示例上测试了它,它输出为0。 现在显而易见的问题是,我如何区分非转换和转换为0?

long int li1;
li1 = strtol("some string with no numbers",NULL,10);
printf("li1: %ld\n",li1);

****
li1: 0

1个回答

69
stdio.h中,strtol的声明如下:
long int strtol(const char *nptr, char **endptr, int base);

strtol 提供了一个强大的错误检查和验证方案,使您能够确定返回的值是 valid 还是 invalid。实际上,您有三个主要工具可用。 (1) 返回的值,(2) 调用时设置的值 errno,以及 (3) 提供给 strtolnptrendptr 的地址和内容。 (请参见完整详情请参阅 man 3 strtol - 在 man 页面的示例也提供了一组更短的条件进行检查,但以下内容已扩展以进行解释)。

在您的情况下,您询问有关返回值为 0 并确定它是否有效的问题。如您所见,strtol 返回的 0不表示读取的数字为 0 或者 0 是有效的。要确定 0 是否有效,您还必须查看调用期间设置的值 errno(如果已设置)。具体而言,如果 errno != 0 并且由 strtol 返回的值为 0,则由 strtol 返回的值是INVALID。(这种情况将表示 invalid baseunderflowoverflow,且 errno 等于 EINVALERANGE)。

还有第二种情况会导致 strtol 返回 INVALID0。即输入中没有读取到数字的情况。当发生这种情况时,strtolendptr == nptr 的值设为 0。因此,在得出输入了 0 时,您还必须检查指针值是否相等。(可以在字符串中键入多个 0 来输入一个VALID0

以下提供了一个简短的示例,说明了评估 strtol 返回值时要检查的不同错误条件以及几种不同的测试条件:

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

int main (int argc, char **argv)
{
    if (argc < 2) {
        fprintf (stderr, "\n Error: insufficient input. Usage: %s int [int (base)]\n\n", argv[0]);
        return 1;
    }

    const char *nptr = argv[1];                     /* string to read               */
    char *endptr = NULL;                            /* pointer to additional chars  */
    int base = (argc > 2) ? atoi (argv[2]) : 10;    /* numeric base (default 10)    */
    long number = 0;                                /* variable holding return      */

    /* reset errno to 0 before call */
    errno = 0;

    /* call to strtol assigning return to number */
    number = strtol (nptr, &endptr, base );

    /* output original string of characters considered */
    printf ("\n string : %s\n base   : %d\n endptr : %s\n\n", nptr, base, endptr);

    /* test return to number and errno values */
    if (nptr == endptr)
        printf (" number : %lu  invalid  (no digits found, 0 returned)\n", number);
    else if (errno == ERANGE && number == LONG_MIN)
        printf (" number : %lu  invalid  (underflow occurred)\n", number);
    else if (errno == ERANGE && number == LONG_MAX)
        printf (" number : %lu  invalid  (overflow occurred)\n", number);
    else if (errno == EINVAL)  /* not in all c99 implementations - gcc OK */
        printf (" number : %lu  invalid  (base contains unsupported value)\n", number);
    else if (errno != 0 && number == 0)
        printf (" number : %lu  invalid  (unspecified error occurred)\n", number);
    else if (errno == 0 && nptr && !*endptr)
        printf (" number : %lu    valid  (and represents all characters read)\n", number);
    else if (errno == 0 && nptr && *endptr != 0)
        printf (" number : %lu    valid  (but additional characters remain)\n", number);

    printf ("\n");

    return 0;
}

输出:

$ ./bin/s2lv 578231

 string : 578231
 base   : 10
 endptr :

 number : 578231    valid  (and represents all characters read)

$ ./bin/s2lv 578231_w_additional_chars

 string : 578231_w_additional_chars
 base   : 10
 endptr : _w_additional_chars

 number : 578231    valid  (but additional characters remain)

$ ./bin/s2lv 578some2more3stuff1

 string : 578some2more3stuff1
 base   : 10
 endptr : some2more3stuff1

 number : 578    valid  (but additional characters remain)

$ ./bin/s2lv 00000000000000000

 string : 00000000000000000
 base   : 10
 endptr :

 number : 0    valid  (and represents all characters read)

$ ./bin/s2lv stuff578231

 string : stuff578231
 base   : 10
 endptr : stuff578231

 number : 0  invalid  (no digits found, 0 returned)

$ ./bin/s2lv 00000000000000000 -2

 string : 00000000000000000
 base   : -2
 endptr : (null)

 number : 0  invalid  (base contains unsupported value)

1
请纠正我,但是对于number和*endptr的初始赋值不是必要的,对吗? - Dean Gurvitz
2
没错,但是养成良好的习惯,比如初始化所有变量(特别是数组),可以避免无意中尝试访问未初始化的值(和未定义行为)。 - David C. Rankin
6
当返回值为0时,可能会设置errno为[EINVAL]。 "robust"(此处未提供上下文,无法确切翻译) - goji
1
关于 EINVAL 的讨论反映了非标准的 C 库实现扩展。对 strtol() 的错误检查不需要涉及 EINVAL 的测试(如“不是所有的 c99 实现 - gcc OK”所建议的),但当存在 EINVAL 时,可能会受益于提供额外的细节。 - chux - Reinstate Monica
对于一个实现来定义本应是未定义行为并设置 errno 是有意义的,例如当 nptr == NULL 或无效的 base 时,因此 else if (errno) 是一个很好的捕获方式。 - chux - Reinstate Monica
显示剩余5条评论

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