strtol 不改变 errno

strtol not changing errno

我正在开发一个程序,该程序在给定一个以 HH:MM:SS 格式表示时间的字符数组的情况下执行计算。它必须解析各个时间单位。 这是我的代码的精简版,只关注时间:

unsigned long parseTime(const char *time)
{
    int base = 10;                    //base 10
    long hours = 60;                  //defaults to something out of range
    char localTime[BUFSIZ]            //declares a local array
    strncpy(localTime, time, BUFSIZ); //copies parameter array to local
    errno = 0;                        //sets errno to 0

    char *par;                        //pointer
    par = strchr(localTime, ':');     //parses to the nearest ':'
    localTime[par - localTime] = '[=10=]';  //sets the ':' to null character

    hours = strtol(localTime, &par, base); //updates hours to parsed numbers in the char array
    printf("errno is: %d\n", errno);       //checks errno
    errno = 0;                             //resets errno to 0
    par++;                                 //moves pointer past the null character
}

问题是如果输入无效(例如 aa:13:13),strtol() 显然没有检测到错误,因为它没有将 errno 更新为 1 ,所以我不能做错误处理。我错了什么?

当无法执行转换时,

strtol 不需要产生错误代码。相反,您应该使用第二个参数来存储转换后的最终位置并将其与初始位置进行比较。

顺便说一句,您的代码中还有许多其他错误不会影响您遇到的问题,但也应该修复这些错误,例如 strncpy.

的不正确使用

正如其他人所解释的那样,strtol 可能不会更新 errno 以防它无法执行任何转换。 C 标准仅记录将 errnor 设置为 ERANGE,以防转换后的值不适合 long 整数。

您的代码还有其他问题:

  • 复制带有 strncpy 的字符串是不正确的:如果源字符串长于 BUFSIZlocalTime 将不会以 null 终止。避免使用 strncpy,一个几乎不符合目的的难以理解的函数。
  • 在这种情况下,您不需要清除:'[=22=]'strtol会停在第一个非数字字符处。 localTime[par - localTime] = '[=24=]'; 写法比较复杂 *par = '[=25=]';

一个更简单的版本是:

long parseTime(const char *time) {
    char *par;
    long hours;

    if (!isdigit((unsigned char)*time) {
        /* invalid format */
        return -1;
    }
    errno = 0;
    hours = strtol(time, &par, 10);
    if (errno != 0) {
        /* overflow */
        return -2;
    }
    /* you may want to check that hour is within a decent range... */
    if (*par != ':') {
        /* invalid format */
        return -3;
    }
    par++;
    /* now you can parse further fields... */
    return hours;
}

我将 return 类型更改为 long,这样您就可以轻松检查无效格式,甚至可以根据负 return 值确定哪个错误。

对于更简单的替代方法,使用 sscanf:

long parseTime(const char *time) {
    unsigned int hours, minutes, seconds;
    char c;

    if (sscanf(time, "%u:%u:%u%c", &hours, &minutes, &seconds, &c) != 3) {
        /* invalid format */
        return -1;
    }
    if (hours > 1000 || minutes > 59 || seconds > 59) {
        /* invalid values */
        return -2;
    }
    return hours * 3600L + minutes * 60 + seconds;
}

这种方法仍然接受不正确的字符串,例如 1: 1: 112:00000002:1。手动解析字符串似乎是最简洁高效的解决方案。

sscanf() 的一个有用技巧是代码可以执行多次传递以检测错误输入:

// HH:MM:SS
int parseTime(const char *hms, unsigned long *secs) {
  int n = 0;
  // Check for valid text
  sscanf(hms "%*[0-2]%*[0-9]:%*[0-5]%*[0-9]:%*[0-5]%*[0-9]%n", &n);
  if (n == 0) return -1; // fail

  // Scan and convert to integers
  unsigned h,m,s;
  sscanf(hms "%u:%u:%u", &h, &m, &s);
  // Range checks as needed
  if (h >= 24 || m >= 60 || s >= 60) return -1;

  *sec = (h*60 + m)*60L + s;
  return 0;
}

hours = strtol(localTime, &par, base);语句之后你必须先保存errno的值。因为在这条语句之后,您将调用 printf() 语句,该语句也会相应地设置 errno

printf("errno is: %d\n", errno); 

所以在这个语句中 "errno" 给出了 printf() 的错误指示,而不是 strtol()... 为此,在调用任何库函数之前保存 "errno" 因为大多数的库函数与 "errno" 交互。 正确的用法是:

hours = strtol(localTime, &par, base);
int saved_error = errno;       // Saving the error...
printf("errno is: %d\n", saved_error);

现在检查一下。它肯定会给出正确的输出......还有一件事是将这个 errno 转换为一些有意义的字符串来表示错误使用 strerror() 函数作为 :

printf("Error is: %s\n", strerror(saved_error));