【问题标题】:Correct usage of strtolstrtol的正确使用
【发布时间】:2012-12-20 00:17:15
【问题描述】:

下面的程序将字符串转换为长字符串,但根据我的理解,它也返回错误。我依赖的事实是,如果strtol 成功将字符串转换为长字符串,那么strtol 的第二个参数应该等于NULL。当我使用 55 运行以下应用程序时,我收到以下消息。

./convertToLong 55
Could not convert 55 to long and leftover string is: 55 as long is 55

如何成功检测 strtol 中的错误?在我的应用程序中,零是一个有效值。

代码:

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

static long parseLong(const char * str);

int main(int argc, char ** argv)
{
    printf("%s as long is %ld\n", argv[1], parseLong(argv[1]));
    return 0;
 }

static long parseLong(const char * str)
{
    long _val = 0;
    char * temp;

    _val = strtol(str, &temp, 0);

    if(temp != '\0')
            printf("Could not convert %s to long and leftover string is: %s", str, temp);

    return _val;
}

【问题讨论】:

  • 再次阅读文档;您还应该处理溢出等错误。
  • 另外,strto* 函数的正确错误检查不是通过检查输出指针来完成的。应该通过检查零返回值和一组errno来完成。
  • 为什么不在 C++ 中使用 std::stoi ? (你添加了 C++ 标签)
  • @BatchyX,对于像“123abc”这样的字符串(就像my previous question 中的共识一样),它不会很好地工作。 OP 正在检查要转换的整个字符串。
  • @chris:你可以用std::stoi做同样的事情。其实stoi的原型和strtol差不多,只是使用了异常到期的异常,而不是带有全局错误变量hackery的错误返回值。

标签: c++ c


【解决方案1】:

你应该检查一下

*temp != '\0'

你也应该可以按照这个调用strotol后查看errno的值:

RETURN VALUES
     The strtol(), strtoll(), strtoimax(), and strtoq() functions return the result
     of the conversion, unless the value would underflow or overflow.  If no conver-
     sion could be performed, 0 is returned and the global variable errno is set to
     EINVAL (the last feature is not portable across all platforms).  If an overflow
     or underflow occurs, errno is set to ERANGE and the function return value is
     clamped according to the following table.


       Function       underflow     overflow
       strtol()       LONG_MIN      LONG_MAX
       strtoll()      LLONG_MIN     LLONG_MAX
       strtoimax()    INTMAX_MIN    INTMAX_MAX
       strtoq()       LLONG_MIN     LLONG_MAX

【讨论】:

  • 如果你不说在哪里可以找到“下表”,那么引用“下表”是没有意义的。
  • 此文档是您自己编写的,还是您忘记提及您复制它的来源?
  • 不,这是手册页。只需在任何基于 unix 的系统上使用“man strtol”即可。
  • 我只是问,因为 NetBSD 手册页看起来完全不同,尽管它是一个类 UNIX 系统。
  • 此外,问题被标记为“C,C++”,因此正确的参考来自 C 或 C++ 标准,而不是来自特定硬件架构上的特定实现。
【解决方案2】:

如何成功检测 strtol 中的错误?

static long parseLong(const char * str) {
    int base = 0;
    char *endptr;
    errno = 0;
    long val = strtol(str, &endptr, base);

标准 C 库指定/支持的 3 个测试:

  1. 任何转换完成了吗?

     if (str == endptr) puts("No conversion.");
    
  2. 在范围内?

     else if (errno == ERANGE) puts("Input out of long range.");
    
  3. 尾随垃圾?

     else if (*endptr) puts("Extra junk after the numeric text.");
    

成功

    else printf("Success %ld\n", val);

str == NULLbase 这样的输入不是0,[2 到36] 是未定义的行为。各种实现(C 库的扩展)通过errno 提供定义的行为和报告。我们可以添加第四个测试。

    else if (errno) puts("Some implementation error found.");

或结合errno == ERANGE 测试。


同样利用常见实现扩展的简洁代码示例。

long my_parseLong(const char *str, int base, bool *success) {
    char *endptr = 0;
    errno = 0;
    long val = strtol(str, &endptr, base);
   
    if (success) {
      *success = endptr != str && errno == 0 && endptr && *endptr == '\0';
    }
    return val;
}

【讨论】:

    【解决方案3】:

    请注意,以下划线开头的名称是为实现保留的;最好避免在代码中使用此类名称。因此,_val 应该只是 val

    当您第一次遇到strtol() 及其亲属的错误处理的完整规范时,它非常复杂,令人惊讶地复杂。你做的一件绝对正确的事情是使用一个函数来调用strtol();在代码中“原始”使用它可能是不正确的。

    由于问题同时使用 C 和 C++ 标记,我将引用 C2011 标准;您可以在 C++ 标准中找到适合自己的措辞。

    ISO/IEC 9899:2011 §7.22.1.4 strtolstrtollstrtoulstrtoull 函数

    long int strtol(const char * restrict nptr, char ** restrict endptr, int base);

    ¶2 [...] 首先, 他们将输入字符串分解为三个部分:一个初始的,可能是空的,序列 空白字符(由 isspace 函数指定),主题序列 类似于一个整数,以由 base 的值确定的某个基数表示,以及 一个或多个无法识别的字符的最终字符串,包括终止的 null 输入字符串的字符。 [...]

    ¶7 如果主题序列为空或不具有预期形式,则不进行转换 执行; nptr 的值存储在endptr 指向的对象中,前提是 endptr 不是空指针。

    返回

    ¶8 strtolstrtollstrtoulstrtoull 函数返回转换后的 价值,如果有的话。如果无法执行转换,则返回零。如果正确的值 超出可表示值的范围,LONG_MIN、LONG_MAX、LLONG_MIN, 返回 LLONG_MAX、ULONG_MAX 或 ULLONG_MAX(根据返回类型 和值的符号(如果有),宏 ERANGE 的值存储在 errno 中。

    请记住,没有任何标准 C 库函数将 errno 设置为 0。因此,为了可靠,您必须在调用 strtol() 之前将 errno 设置为零。

    因此,您的 parseLong() 函数可能如下所示:

    static long parseLong(const char *str)
    {
        errno = 0;
        char *temp;
        long val = strtol(str, &temp, 0);
    
        if (temp == str || *temp != '\0' ||
            ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
            fprintf(stderr, "Could not convert '%s' to long and leftover string is: '%s'\n",
                    str, temp);
            // cerr << "Could not convert '" << str << "' to long and leftover string is '"
            //      << temp << "'\n";
        return val;
    }
    

    请注意,如果出错,这将返回 0 或 LONG_MIN 或 LONG_MAX,具体取决于 strtol() 返回的内容。如果你的调用代码需要知道转换是否成功,你需要一个不同的函数接口——见下文。另外,请注意错误应该打印到stderr而不是stdout,并且错误消息应该由换行符\n终止;如果不是,则不能保证它们会及时出现。

    现在,在库代码中您可能不希望打印,而您的调用代码可能想知道转换是否成功,因此您也可以修改接口。在这种情况下,您可能会修改该函数,使其返回成功/失败指示:

    bool parseLong(const char *str, long *val)
    {
        char *temp;
        bool rc = true;
        errno = 0;
        *val = strtol(str, &temp, 0);
    
        if (temp == str || *temp != '\0' ||
            ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE))
            rc = false;
    
        return rc;
    }
    

    你可以这样使用:

    if (parseLong(str, &value))
        …conversion successful…
    else
        …handle error…
    

    如果您需要区分“尾随垃圾”、“无效数字字符串”、“值太大”和“值太小”(以及“无错误”),您可以使用整数或 enum一个布尔返回码。如果您希望允许尾随空格但不允许其他字符,或者如果您不想允许任何前导空格,则该函数还有更多工作要做。代码允许八进制、十进制和十六进制;如果你想要严格的十进制,你需要将调用中的 0 更改为 10 到 strtol()

    如果您的函数要伪装成标准库的一部分,它们不应将errno 永久设置为0,因此您需要包装代码以保留errno

    int saved = errno;  // At the start, before errno = 0;
    
    …rest of function…
    
    if (errno == 0)     // Before the return
        errno = saved;
    

    【讨论】:

    • 除了02..36 之外,标准没有提到为base 的值设置errno == EINVAL,但这是合理的做法。通常,您应该谨慎尝试使用errno 检测错误情况,而不是从函数返回;即使函数成功,库也可以将errno 设置为非零值。 (在 Solaris 上,如果输出不是终端,您会在成功操作后找到 errno == ENOTTY。)理论上,strtol() 可以将 "1" 转换为 1 并将 errno 设置为非零价值,这将是合法的但变态(并且成功)。
    • 是否有原因errno == ERANGE被无条件检查,strtol是否返回LONG_MIN/LONG_MAX? (由于您在评论中给出的原因,库函数可能会将 errno 设置为成功。)
    • 您的示例中有错误。 vallong int *,但你检查val == LONG_MIN,它应该是*val == LONG_MIN...
    • 不同意“即使函数成功,库也可以将 errno 设置为非零值。” C11 §7.5 3 讨论了这一点,但这不适用于strtol(),因为“如果函数描述中没有记录 errno 的使用”,strtol() 就是这样做的。 if (temp == str || *temp != '\0' || errno == ERANGE) 就足够了。 IMO if (temp == str || *temp != '\0' || errno) 更好,因为它可以捕获一些 ID 扩展名。不需要(*val == LONG_MIN || *val == LONG_MAX)
    • @JonathanLeffler 同意EINVAL 和建议的temp == str || *temp != '\0' || errno - 我认为我们对此非常同意。然而,评论是关于*val == LONG_MIN || *val == LONG_MAX 的需要,考虑到其他 errno 的可能性,它并没有得到增强。如果errno == ERANGE 为真,那么即使*val == LONG_MIN || *val == LONG_MAX 在某些独角兽机器上为假,strtol() 仍应视为失败。
    【解决方案4】:

    您缺少一个间接级别。您想检查 字符 是否是终止 NUL,而不是指针是否为 NULL

    if (*temp != '\0')
    

    顺便说一句,这不是错误检查的好方法。 strto* 系列函数的正确错误检查方法不是通过将输出指针与字符串末尾进行比较来完成的。应该通过检查零返回值并获取返回值errno 来完成。

    【讨论】:

      【解决方案5】:

      你快到了。 temp本身不会为空,但是如果整个字符串被转换,它会指向一个空字符,所以需要解引用:

      if (*temp != '\0')
      

      【讨论】:

      • 需要额外的检查来处理溢出和解析空字符串。请参阅 Jonathan Leffler 的回答。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-02
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      • 2013-04-08
      • 2013-01-25
      相关资源
      最近更新 更多