繁体   English   中英

strtol 的正确使用

[英]Correct usage of strtol

下面的程序将字符串转换为 long,但根据我的理解,它也会返回错误。 我所依赖的事实是,如果strtol成功地将字符串转换为 long,那么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;
}

请注意,以下划线开头的名称是为实现保留的; 最好避免在代码中使用此类名称。 因此, _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 的值和一个或多个无法识别的字符组成的最终字符串,包括输入字符串的终止空字符。 [...]

¶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而不是布尔返回码. 如果您希望允许尾随空格但不允许其他字符,或者如果您不想允许任何前导空格,则在该函数中还有更多工作要做。 代码允许八进制、十进制和十六进制; 如果您想要严格的十进制,则需要在对strtol()的调用中将 0 更改为 10 。

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

int saved = errno;  // At the start, before errno = 0;

…rest of function…

if (errno == 0)     // Before the return
    errno = saved;

您快到了。 temp本身不会为空,但如果整个字符串被转换,它将指向一个空字符,因此您需要取消引用它:

if (*temp != '\0')

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

if (*temp != '\0')

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

如何成功检测 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 not 0, [2 to 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;
}

你应该检查

*temp != '\0'

您还应该能够根据以下内容在调用 strool 后检查 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM